deleted the old marts

This commit is contained in:
Sebastian Moser
2026-10-22 22:13:26 +02:00
parent 679f5e51e5
commit 7f86f3dc5e
64 changed files with 3 additions and 9540 deletions

View File

@@ -17,6 +17,9 @@ packages/
└── macros/ # Mize macros
```
## Important
- usage of any git commands is strictly forbidden!
## Key Components
All code written for, with or as part as mize (except for the small core) is written as a Mize Part. see `docs/part-system.md` for how that works.

View File

@@ -1,65 +0,0 @@
[package]
name = "marts"
description = "marts = mize_parts - parts collection of the Mize framework"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
clap = { version = "4.5.59", optional = true, features = ["string"] }
flume = "0.12.0"
futures = "0.3.32"
mize = { path = "../", default-features = false, features = [] }
tokio = { version = "1.49.0", default-features = false, features = ["time"] }
# https://github.com/rscarson/rustyscript/pull/415
#rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
#deno_core = { version = "0.380.1", optional = true }
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
serde = "1.0.228"
serde_json = "1.0.149"
rusqlite = { version = "0.37", features = ["bundled"], optional = true }
dioxus = { version = "0.7.1", optional = true }
spacetimedb-sdk = { version = "2.6.0", default-features = false }
generated = { path = "./generated" }
webbrowser = { version = "1.2.0", optional = true }
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
tracing = "0.1.40"
wasm-bindgen = { version = "0.2.84", optional = true }
wasm-bindgen-futures = { version = "0.4.49", optional = true }
web-sys = { version = "0.3.70", optional = true, features = [
"Window",
"Storage",
"console"
]}
js-sys = { version = "0.3.70", optional = true }
spacetimedb = { version = "2.6.0", features = ["unstable"] }
log = "0.4"
chrono = { version = "0.4.45", features = [], default-features = false }
chrono-tz = { version = "0.10.4", default-features = false }
url = "2.5.8"
serenity = { version = "0.12.5", optional = true }
matrix-sdk = "0.18.0"
axum = { version = "0.8.9", optional = true }
dioxus-ssr = { version = "0.7.10", optional = true }
axum-oidc = { version = "=1.0.0-dev-1", optional = true }
tower = { version = "0.5.3", optional = true }
tower-sessions = { version = "0.15.0", optional = true }
tower-http = { version = "0.7.1", optional = true, features = [ "cors", "trace" ] }
cmd_lib = "2.0.1"
[features]
default = ["target-os", "desktop", "server", "client"]
#default = ["target-js", "target-obsidian"] # to get rust analyzer to analyzer js only parts
target-spacetime = [ "mize/target-js", "spacetimedb-sdk/browser" ]
target-os = [ "clap", "mize/target-os", "rusqlite", "dioxus", "dep:webbrowser" ]
target-js = [ "mize/target-js", "spacetimedb-sdk/browser", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "js-sys", "generated/target-js", "web-sys/Storage", "web-sys/Window", "dioxus" ]
target-obsidian = [ "target-js" ]
desktop = ["dioxus/desktop"]
client = []
server = [ "axum", "dioxus-ssr", "axum-oidc", "tower", "tower-sessions", "tower-http" ]
clap = ["dep:clap"]
serenity = ["dep:serenity"]

View File

@@ -1,8 +0,0 @@
{
"name": "@ppc/marts",
"imports": {},
"exports": {
".": "./src/index.ts"
},
"tasks": {}
}

View File

@@ -1,12 +0,0 @@
import * as esbuild from "npm:esbuild";
import { denoPlugin } from "jsr:@deno/esbuild-plugin";
await esbuild.build({
plugins: [denoPlugin()],
entryPoints: ["./habitica.ts"],
outfile: "./deno_dist/habitica.js",
bundle: true,
format: "iife",
});
esbuild.stop();

View File

@@ -1,14 +0,0 @@
[package]
name = "generated"
version = "0.1.0"
edition = "2021"
[lib]
name = "generated"
path = "src/lib.rs"
[dependencies]
spacetimedb-sdk = "2.6.0"
[features]
target-js = [ "spacetimedb-sdk/browser" ]

View File

@@ -1 +0,0 @@
pub mod spacetime;

View File

@@ -1,60 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Action {
pub name: String,
pub activity: Option<u64>,
pub task: Option<u64>,
pub start: Option<__sdk::Timestamp>,
pub end: Option<__sdk::Timestamp>,
pub duration: Option<u64>,
}
impl __sdk::InModule for Action {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Action`.
///
/// Provides typed access to columns for query building.
pub struct ActionCols {
pub name: __sdk::__query_builder::Col<Action, String>,
pub activity: __sdk::__query_builder::Col<Action, Option<u64>>,
pub task: __sdk::__query_builder::Col<Action, Option<u64>>,
pub start: __sdk::__query_builder::Col<Action, Option<__sdk::Timestamp>>,
pub end: __sdk::__query_builder::Col<Action, Option<__sdk::Timestamp>>,
pub duration: __sdk::__query_builder::Col<Action, Option<u64>>,
}
impl __sdk::__query_builder::HasCols for Action {
type Cols = ActionCols;
fn cols(table_name: &'static str) -> Self::Cols {
ActionCols {
name: __sdk::__query_builder::Col::new(table_name, "name"),
activity: __sdk::__query_builder::Col::new(table_name, "activity"),
task: __sdk::__query_builder::Col::new(table_name, "task"),
start: __sdk::__query_builder::Col::new(table_name, "start"),
end: __sdk::__query_builder::Col::new(table_name, "end"),
duration: __sdk::__query_builder::Col::new(table_name, "duration"),
}
}
}
/// Indexed column accessor struct for the table `Action`.
///
/// Provides typed access to indexed columns for query building.
pub struct ActionIxCols {}
impl __sdk::__query_builder::HasIxCols for Action {
type IxCols = ActionIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
ActionIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Action {}

View File

@@ -1,159 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::activity_type::Activity;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `activities`.
///
/// Obtain a handle from the [`ActivitiesTableAccess::activities`] method on [`super::RemoteTables`],
/// like `ctx.db.activities()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.activities().on_insert(...)`.
pub struct ActivitiesTableHandle<'ctx> {
imp: __sdk::TableHandle<Activity>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `activities`.
///
/// Implemented for [`super::RemoteTables`].
pub trait ActivitiesTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`ActivitiesTableHandle`], which mediates access to the table `activities`.
fn activities(&self) -> ActivitiesTableHandle<'_>;
}
impl ActivitiesTableAccess for super::RemoteTables {
fn activities(&self) -> ActivitiesTableHandle<'_> {
ActivitiesTableHandle {
imp: self.imp.get_table::<Activity>("activities"),
ctx: std::marker::PhantomData,
}
}
}
pub struct ActivitiesInsertCallbackId(__sdk::CallbackId);
pub struct ActivitiesDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for ActivitiesTableHandle<'ctx> {
type Row = Activity;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Activity> + '_ {
self.imp.iter()
}
type InsertCallbackId = ActivitiesInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ActivitiesInsertCallbackId {
ActivitiesInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: ActivitiesInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = ActivitiesDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ActivitiesDeleteCallbackId {
ActivitiesDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: ActivitiesDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct ActivitiesUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for ActivitiesTableHandle<'ctx> {
type UpdateCallbackId = ActivitiesUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> ActivitiesUpdateCallbackId {
ActivitiesUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: ActivitiesUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `id` unique index on the table `activities`,
/// which allows point queries on the field of the same name
/// via the [`ActivitiesIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.activities().id().find(...)`.
pub struct ActivitiesIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Activity, u64>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> ActivitiesTableHandle<'ctx> {
/// Get a handle on the `id` unique index on the table `activities`.
pub fn id(&self) -> ActivitiesIdUnique<'ctx> {
ActivitiesIdUnique {
imp: self.imp.get_unique_constraint::<u64>("id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> ActivitiesIdUnique<'ctx> {
/// Find the subscribed row whose `id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &u64) -> Option<Activity> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Activity>("activities");
_table.add_unique_constraint::<u64>("id", |row| &row.id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Activity>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Activity>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Activity`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait activitiesQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Activity`.
fn activities(&self) -> __sdk::__query_builder::Table<Activity>;
}
impl activitiesQueryTableAccess for __sdk::QueryTableAccessor {
fn activities(&self) -> __sdk::__query_builder::Table<Activity> {
__sdk::__query_builder::Table::new("activities")
}
}

View File

@@ -1,58 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Activity {
pub id: u64,
pub name: String,
pub task: Option<u64>,
pub parents_ids: Vec<u64>,
}
impl __sdk::InModule for Activity {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Activity`.
///
/// Provides typed access to columns for query building.
pub struct ActivityCols {
pub id: __sdk::__query_builder::Col<Activity, u64>,
pub name: __sdk::__query_builder::Col<Activity, String>,
pub task: __sdk::__query_builder::Col<Activity, Option<u64>>,
pub parents_ids: __sdk::__query_builder::Col<Activity, Vec<u64>>,
}
impl __sdk::__query_builder::HasCols for Activity {
type Cols = ActivityCols;
fn cols(table_name: &'static str) -> Self::Cols {
ActivityCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
name: __sdk::__query_builder::Col::new(table_name, "name"),
task: __sdk::__query_builder::Col::new(table_name, "task"),
parents_ids: __sdk::__query_builder::Col::new(table_name, "parents_ids"),
}
}
}
/// Indexed column accessor struct for the table `Activity`.
///
/// Provides typed access to indexed columns for query building.
pub struct ActivityIxCols {
pub id: __sdk::__query_builder::IxCol<Activity, u64>,
}
impl __sdk::__query_builder::HasIxCols for Activity {
type IxCols = ActivityIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
ActivityIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Activity {}

View File

@@ -1,66 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct AddActivityArgs {
pub name: String,
}
impl From<AddActivityArgs> for super::Reducer {
fn from(args: AddActivityArgs) -> Self {
Self::AddActivity { name: args.name }
}
}
impl __sdk::InModule for AddActivityArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `add_activity`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait add_activity {
/// Request that the remote module invoke the reducer `add_activity` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`add_activity:add_activity_then`] to run a callback after the reducer completes.
fn add_activity(&self, name: String) -> __sdk::Result<()> {
self.add_activity_then(name, |_, _| {})
}
/// Request that the remote module invoke the reducer `add_activity` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn add_activity_then(
&self,
name: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl add_activity for super::RemoteReducers {
fn add_activity_then(
&self,
name: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(AddActivityArgs { name }, callback)
}
}

View File

@@ -1,68 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct AddThoughtArgs {
pub thought: String,
}
impl From<AddThoughtArgs> for super::Reducer {
fn from(args: AddThoughtArgs) -> Self {
Self::AddThought {
thought: args.thought,
}
}
}
impl __sdk::InModule for AddThoughtArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `add_thought`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait add_thought {
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`add_thought:add_thought_then`] to run a callback after the reducer completes.
fn add_thought(&self, thought: String) -> __sdk::Result<()> {
self.add_thought_then(thought, |_, _| {})
}
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn add_thought_then(
&self,
thought: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl add_thought for super::RemoteReducers {
fn add_thought_then(
&self,
thought: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(AddThoughtArgs { thought }, callback)
}
}

View File

@@ -1,111 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::daily_task_log_type::DailyTaskLog;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `daily_task_log`.
///
/// Obtain a handle from the [`DailyTaskLogTableAccess::daily_task_log`] method on [`super::RemoteTables`],
/// like `ctx.db.daily_task_log()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.daily_task_log().on_insert(...)`.
pub struct DailyTaskLogTableHandle<'ctx> {
imp: __sdk::TableHandle<DailyTaskLog>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `daily_task_log`.
///
/// Implemented for [`super::RemoteTables`].
pub trait DailyTaskLogTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`DailyTaskLogTableHandle`], which mediates access to the table `daily_task_log`.
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_>;
}
impl DailyTaskLogTableAccess for super::RemoteTables {
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_> {
DailyTaskLogTableHandle {
imp: self.imp.get_table::<DailyTaskLog>("daily_task_log"),
ctx: std::marker::PhantomData,
}
}
}
pub struct DailyTaskLogInsertCallbackId(__sdk::CallbackId);
pub struct DailyTaskLogDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for DailyTaskLogTableHandle<'ctx> {
type Row = DailyTaskLog;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = DailyTaskLog> + '_ {
self.imp.iter()
}
type InsertCallbackId = DailyTaskLogInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskLogInsertCallbackId {
DailyTaskLogInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: DailyTaskLogInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = DailyTaskLogDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskLogDeleteCallbackId {
DailyTaskLogDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: DailyTaskLogDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<DailyTaskLog>("daily_task_log");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<DailyTaskLog>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<DailyTaskLog>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `DailyTaskLog`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait daily_task_logQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `DailyTaskLog`.
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog>;
}
impl daily_task_logQueryTableAccess for __sdk::QueryTableAccessor {
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog> {
__sdk::__query_builder::Table::new("daily_task_log")
}
}

View File

@@ -1,48 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct DailyTaskLog {
pub daily_task_id: i64,
pub done: __sdk::Timestamp,
}
impl __sdk::InModule for DailyTaskLog {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `DailyTaskLog`.
///
/// Provides typed access to columns for query building.
pub struct DailyTaskLogCols {
pub daily_task_id: __sdk::__query_builder::Col<DailyTaskLog, i64>,
pub done: __sdk::__query_builder::Col<DailyTaskLog, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for DailyTaskLog {
type Cols = DailyTaskLogCols;
fn cols(table_name: &'static str) -> Self::Cols {
DailyTaskLogCols {
daily_task_id: __sdk::__query_builder::Col::new(table_name, "daily_task_id"),
done: __sdk::__query_builder::Col::new(table_name, "done"),
}
}
}
/// Indexed column accessor struct for the table `DailyTaskLog`.
///
/// Provides typed access to indexed columns for query building.
pub struct DailyTaskLogIxCols {}
impl __sdk::__query_builder::HasIxCols for DailyTaskLog {
type IxCols = DailyTaskLogIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
DailyTaskLogIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for DailyTaskLog {}

View File

@@ -1,111 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::daily_task_type::DailyTask;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `daily_task`.
///
/// Obtain a handle from the [`DailyTaskTableAccess::daily_task`] method on [`super::RemoteTables`],
/// like `ctx.db.daily_task()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.daily_task().on_insert(...)`.
pub struct DailyTaskTableHandle<'ctx> {
imp: __sdk::TableHandle<DailyTask>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `daily_task`.
///
/// Implemented for [`super::RemoteTables`].
pub trait DailyTaskTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`DailyTaskTableHandle`], which mediates access to the table `daily_task`.
fn daily_task(&self) -> DailyTaskTableHandle<'_>;
}
impl DailyTaskTableAccess for super::RemoteTables {
fn daily_task(&self) -> DailyTaskTableHandle<'_> {
DailyTaskTableHandle {
imp: self.imp.get_table::<DailyTask>("daily_task"),
ctx: std::marker::PhantomData,
}
}
}
pub struct DailyTaskInsertCallbackId(__sdk::CallbackId);
pub struct DailyTaskDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for DailyTaskTableHandle<'ctx> {
type Row = DailyTask;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = DailyTask> + '_ {
self.imp.iter()
}
type InsertCallbackId = DailyTaskInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskInsertCallbackId {
DailyTaskInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: DailyTaskInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = DailyTaskDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskDeleteCallbackId {
DailyTaskDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: DailyTaskDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<DailyTask>("daily_task");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<DailyTask>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<DailyTask>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `DailyTask`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait daily_taskQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `DailyTask`.
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask>;
}
impl daily_taskQueryTableAccess for __sdk::QueryTableAccessor {
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask> {
__sdk::__query_builder::Table::new("daily_task")
}
}

View File

@@ -1,51 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct DailyTask {
pub id: i64,
pub name: String,
pub owner: __sdk::Identity,
}
impl __sdk::InModule for DailyTask {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `DailyTask`.
///
/// Provides typed access to columns for query building.
pub struct DailyTaskCols {
pub id: __sdk::__query_builder::Col<DailyTask, i64>,
pub name: __sdk::__query_builder::Col<DailyTask, String>,
pub owner: __sdk::__query_builder::Col<DailyTask, __sdk::Identity>,
}
impl __sdk::__query_builder::HasCols for DailyTask {
type Cols = DailyTaskCols;
fn cols(table_name: &'static str) -> Self::Cols {
DailyTaskCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
name: __sdk::__query_builder::Col::new(table_name, "name"),
owner: __sdk::__query_builder::Col::new(table_name, "owner"),
}
}
}
/// Indexed column accessor struct for the table `DailyTask`.
///
/// Provides typed access to indexed columns for query building.
pub struct DailyTaskIxCols {}
impl __sdk::__query_builder::HasIxCols for DailyTask {
type IxCols = DailyTaskIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
DailyTaskIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for DailyTask {}

View File

@@ -1,59 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ExportTimetrackArgs {
pub username: String,
pub date: String,
}
impl __sdk::InModule for ExportTimetrackArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `export_timetrack`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait export_timetrack {
fn export_timetrack(&self, username: String, date: String) {
self.export_timetrack_then(username, date, |_, _| {});
}
fn export_timetrack_then(
&self,
username: String,
date: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl export_timetrack for super::RemoteProcedures {
fn export_timetrack_then(
&self,
username: String,
date: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
"export_timetrack",
ExportTimetrackArgs { username, date },
__callback,
);
}
}

View File

@@ -1,56 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct FirstTimetrackDateArgs {
pub username: String,
}
impl __sdk::InModule for FirstTimetrackDateArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `first_timetrack_date`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait first_timetrack_date {
fn first_timetrack_date(&self, username: String) {
self.first_timetrack_date_then(username, |_, _| {});
}
fn first_timetrack_date_then(
&self,
username: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl first_timetrack_date for super::RemoteProcedures {
fn first_timetrack_date_then(
&self,
username: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
"first_timetrack_date",
FirstTimetrackDateArgs { username },
__callback,
);
}
}

View File

@@ -1,63 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::action_type::Action;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct LogEvent {
pub id: u64,
pub start: __sdk::Timestamp,
pub end: __sdk::Timestamp,
pub action: Action,
pub user: __sdk::Identity,
}
impl __sdk::InModule for LogEvent {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `LogEvent`.
///
/// Provides typed access to columns for query building.
pub struct LogEventCols {
pub id: __sdk::__query_builder::Col<LogEvent, u64>,
pub start: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
pub end: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
pub action: __sdk::__query_builder::Col<LogEvent, Action>,
pub user: __sdk::__query_builder::Col<LogEvent, __sdk::Identity>,
}
impl __sdk::__query_builder::HasCols for LogEvent {
type Cols = LogEventCols;
fn cols(table_name: &'static str) -> Self::Cols {
LogEventCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
start: __sdk::__query_builder::Col::new(table_name, "start"),
end: __sdk::__query_builder::Col::new(table_name, "end"),
action: __sdk::__query_builder::Col::new(table_name, "action"),
user: __sdk::__query_builder::Col::new(table_name, "user"),
}
}
}
/// Indexed column accessor struct for the table `LogEvent`.
///
/// Provides typed access to indexed columns for query building.
pub struct LogEventIxCols {
pub id: __sdk::__query_builder::IxCol<LogEvent, u64>,
}
impl __sdk::__query_builder::HasIxCols for LogEvent {
type IxCols = LogEventIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
LogEventIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for LogEvent {}

View File

@@ -1,160 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::action_type::Action;
use super::log_event_type::LogEvent;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `log_events`.
///
/// Obtain a handle from the [`LogEventsTableAccess::log_events`] method on [`super::RemoteTables`],
/// like `ctx.db.log_events()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.log_events().on_insert(...)`.
pub struct LogEventsTableHandle<'ctx> {
imp: __sdk::TableHandle<LogEvent>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `log_events`.
///
/// Implemented for [`super::RemoteTables`].
pub trait LogEventsTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`LogEventsTableHandle`], which mediates access to the table `log_events`.
fn log_events(&self) -> LogEventsTableHandle<'_>;
}
impl LogEventsTableAccess for super::RemoteTables {
fn log_events(&self) -> LogEventsTableHandle<'_> {
LogEventsTableHandle {
imp: self.imp.get_table::<LogEvent>("log_events"),
ctx: std::marker::PhantomData,
}
}
}
pub struct LogEventsInsertCallbackId(__sdk::CallbackId);
pub struct LogEventsDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for LogEventsTableHandle<'ctx> {
type Row = LogEvent;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = LogEvent> + '_ {
self.imp.iter()
}
type InsertCallbackId = LogEventsInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> LogEventsInsertCallbackId {
LogEventsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: LogEventsInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = LogEventsDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> LogEventsDeleteCallbackId {
LogEventsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: LogEventsDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct LogEventsUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for LogEventsTableHandle<'ctx> {
type UpdateCallbackId = LogEventsUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> LogEventsUpdateCallbackId {
LogEventsUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: LogEventsUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `id` unique index on the table `log_events`,
/// which allows point queries on the field of the same name
/// via the [`LogEventsIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.log_events().id().find(...)`.
pub struct LogEventsIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<LogEvent, u64>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> LogEventsTableHandle<'ctx> {
/// Get a handle on the `id` unique index on the table `log_events`.
pub fn id(&self) -> LogEventsIdUnique<'ctx> {
LogEventsIdUnique {
imp: self.imp.get_unique_constraint::<u64>("id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> LogEventsIdUnique<'ctx> {
/// Find the subscribed row whose `id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &u64) -> Option<LogEvent> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<LogEvent>("log_events");
_table.add_unique_constraint::<u64>("id", |row| &row.id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<LogEvent>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<LogEvent>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `LogEvent`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait log_eventsQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `LogEvent`.
fn log_events(&self) -> __sdk::__query_builder::Table<LogEvent>;
}
impl log_eventsQueryTableAccess for __sdk::QueryTableAccessor {
fn log_events(&self) -> __sdk::__query_builder::Table<LogEvent> {
__sdk::__query_builder::Table::new("log_events")
}
}

View File

@@ -1,44 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct MigrateArgs {}
impl __sdk::InModule for MigrateArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `migrate`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait migrate {
fn migrate(&self) {
self.migrate_then(|_, _| {});
}
fn migrate_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
);
}
impl migrate for super::RemoteProcedures {
fn migrate_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, String>("migrate", MigrateArgs {}, __callback);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::action_type::Action;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct NextActionArgs {
pub break_flag: bool,
pub action: Option<Action>,
}
impl From<NextActionArgs> for super::Reducer {
fn from(args: NextActionArgs) -> Self {
Self::NextAction {
break_flag: args.break_flag,
action: args.action,
}
}
}
impl __sdk::InModule for NextActionArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `next_action`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait next_action {
/// Request that the remote module invoke the reducer `next_action` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`next_action:next_action_then`] to run a callback after the reducer completes.
fn next_action(&self, break_flag: bool, action: Option<Action>) -> __sdk::Result<()> {
self.next_action_then(break_flag, action, |_, _| {})
}
/// Request that the remote module invoke the reducer `next_action` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn next_action_then(
&self,
break_flag: bool,
action: Option<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl next_action for super::RemoteReducers {
fn next_action_then(
&self,
break_flag: bool,
action: Option<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(NextActionArgs { break_flag, action }, callback)
}
}

View File

@@ -1,54 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Profile {
pub identity: __sdk::Identity,
pub username: String,
}
impl __sdk::InModule for Profile {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Profile`.
///
/// Provides typed access to columns for query building.
pub struct ProfileCols {
pub identity: __sdk::__query_builder::Col<Profile, __sdk::Identity>,
pub username: __sdk::__query_builder::Col<Profile, String>,
}
impl __sdk::__query_builder::HasCols for Profile {
type Cols = ProfileCols;
fn cols(table_name: &'static str) -> Self::Cols {
ProfileCols {
identity: __sdk::__query_builder::Col::new(table_name, "identity"),
username: __sdk::__query_builder::Col::new(table_name, "username"),
}
}
}
/// Indexed column accessor struct for the table `Profile`.
///
/// Provides typed access to indexed columns for query building.
pub struct ProfileIxCols {
pub identity: __sdk::__query_builder::IxCol<Profile, __sdk::Identity>,
pub username: __sdk::__query_builder::IxCol<Profile, String>,
}
impl __sdk::__query_builder::HasIxCols for Profile {
type IxCols = ProfileIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
ProfileIxCols {
identity: __sdk::__query_builder::IxCol::new(table_name, "identity"),
username: __sdk::__query_builder::IxCol::new(table_name, "username"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Profile {}

View File

@@ -1,192 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::profile_type::Profile;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `profiles`.
///
/// Obtain a handle from the [`ProfilesTableAccess::profiles`] method on [`super::RemoteTables`],
/// like `ctx.db.profiles()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.profiles().on_insert(...)`.
pub struct ProfilesTableHandle<'ctx> {
imp: __sdk::TableHandle<Profile>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `profiles`.
///
/// Implemented for [`super::RemoteTables`].
pub trait ProfilesTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`ProfilesTableHandle`], which mediates access to the table `profiles`.
fn profiles(&self) -> ProfilesTableHandle<'_>;
}
impl ProfilesTableAccess for super::RemoteTables {
fn profiles(&self) -> ProfilesTableHandle<'_> {
ProfilesTableHandle {
imp: self.imp.get_table::<Profile>("profiles"),
ctx: std::marker::PhantomData,
}
}
}
pub struct ProfilesInsertCallbackId(__sdk::CallbackId);
pub struct ProfilesDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for ProfilesTableHandle<'ctx> {
type Row = Profile;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Profile> + '_ {
self.imp.iter()
}
type InsertCallbackId = ProfilesInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ProfilesInsertCallbackId {
ProfilesInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: ProfilesInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = ProfilesDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ProfilesDeleteCallbackId {
ProfilesDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: ProfilesDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct ProfilesUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for ProfilesTableHandle<'ctx> {
type UpdateCallbackId = ProfilesUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> ProfilesUpdateCallbackId {
ProfilesUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: ProfilesUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `identity` unique index on the table `profiles`,
/// which allows point queries on the field of the same name
/// via the [`ProfilesIdentityUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.profiles().identity().find(...)`.
pub struct ProfilesIdentityUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Profile, __sdk::Identity>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> ProfilesTableHandle<'ctx> {
/// Get a handle on the `identity` unique index on the table `profiles`.
pub fn identity(&self) -> ProfilesIdentityUnique<'ctx> {
ProfilesIdentityUnique {
imp: self
.imp
.get_unique_constraint::<__sdk::Identity>("identity"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> ProfilesIdentityUnique<'ctx> {
/// Find the subscribed row whose `identity` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &__sdk::Identity) -> Option<Profile> {
self.imp.find(col_val)
}
}
/// Access to the `username` unique index on the table `profiles`,
/// which allows point queries on the field of the same name
/// via the [`ProfilesUsernameUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.profiles().username().find(...)`.
pub struct ProfilesUsernameUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Profile, String>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> ProfilesTableHandle<'ctx> {
/// Get a handle on the `username` unique index on the table `profiles`.
pub fn username(&self) -> ProfilesUsernameUnique<'ctx> {
ProfilesUsernameUnique {
imp: self.imp.get_unique_constraint::<String>("username"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> ProfilesUsernameUnique<'ctx> {
/// Find the subscribed row whose `username` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &String) -> Option<Profile> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Profile>("profiles");
_table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity);
_table.add_unique_constraint::<String>("username", |row| &row.username);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Profile>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Profile>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Profile`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait profilesQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Profile`.
fn profiles(&self) -> __sdk::__query_builder::Table<Profile>;
}
impl profilesQueryTableAccess for __sdk::QueryTableAccessor {
fn profiles(&self) -> __sdk::__query_builder::Table<Profile> {
__sdk::__query_builder::Table::new("profiles")
}
}

View File

@@ -1,70 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::activity_type::Activity;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct RemoveActivityArgs {
pub activity: Activity,
}
impl From<RemoveActivityArgs> for super::Reducer {
fn from(args: RemoveActivityArgs) -> Self {
Self::RemoveActivity {
activity: args.activity,
}
}
}
impl __sdk::InModule for RemoveActivityArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `remove_activity`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait remove_activity {
/// Request that the remote module invoke the reducer `remove_activity` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`remove_activity:remove_activity_then`] to run a callback after the reducer completes.
fn remove_activity(&self, activity: Activity) -> __sdk::Result<()> {
self.remove_activity_then(activity, |_, _| {})
}
/// Request that the remote module invoke the reducer `remove_activity` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn remove_activity_then(
&self,
activity: Activity,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl remove_activity for super::RemoteReducers {
fn remove_activity_then(
&self,
activity: Activity,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(RemoveActivityArgs { activity }, callback)
}
}

View File

@@ -1,72 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct SetActivityNameArgs {
pub id: u64,
pub name: String,
}
impl From<SetActivityNameArgs> for super::Reducer {
fn from(args: SetActivityNameArgs) -> Self {
Self::SetActivityName {
id: args.id,
name: args.name,
}
}
}
impl __sdk::InModule for SetActivityNameArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `set_activity_name`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait set_activity_name {
/// Request that the remote module invoke the reducer `set_activity_name` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`set_activity_name:set_activity_name_then`] to run a callback after the reducer completes.
fn set_activity_name(&self, id: u64, name: String) -> __sdk::Result<()> {
self.set_activity_name_then(id, name, |_, _| {})
}
/// Request that the remote module invoke the reducer `set_activity_name` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn set_activity_name_then(
&self,
id: u64,
name: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl set_activity_name for super::RemoteReducers {
fn set_activity_name_then(
&self,
id: u64,
name: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(SetActivityNameArgs { id, name }, callback)
}
}

View File

@@ -1,162 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::action_type::Action;
use super::task_part_type::TaskPart;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `task_part`.
///
/// Obtain a handle from the [`TaskPartTableAccess::task_part`] method on [`super::RemoteTables`],
/// like `ctx.db.task_part()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.task_part().on_insert(...)`.
pub struct TaskPartTableHandle<'ctx> {
imp: __sdk::TableHandle<TaskPart>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `task_part`.
///
/// Implemented for [`super::RemoteTables`].
pub trait TaskPartTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`TaskPartTableHandle`], which mediates access to the table `task_part`.
fn task_part(&self) -> TaskPartTableHandle<'_>;
}
impl TaskPartTableAccess for super::RemoteTables {
fn task_part(&self) -> TaskPartTableHandle<'_> {
TaskPartTableHandle {
imp: self.imp.get_table::<TaskPart>("task_part"),
ctx: std::marker::PhantomData,
}
}
}
pub struct TaskPartInsertCallbackId(__sdk::CallbackId);
pub struct TaskPartDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for TaskPartTableHandle<'ctx> {
type Row = TaskPart;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = TaskPart> + '_ {
self.imp.iter()
}
type InsertCallbackId = TaskPartInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TaskPartInsertCallbackId {
TaskPartInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: TaskPartInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = TaskPartDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TaskPartDeleteCallbackId {
TaskPartDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: TaskPartDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct TaskPartUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for TaskPartTableHandle<'ctx> {
type UpdateCallbackId = TaskPartUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> TaskPartUpdateCallbackId {
TaskPartUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: TaskPartUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `identity` unique index on the table `task_part`,
/// which allows point queries on the field of the same name
/// via the [`TaskPartIdentityUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.task_part().identity().find(...)`.
pub struct TaskPartIdentityUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<TaskPart, __sdk::Identity>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> TaskPartTableHandle<'ctx> {
/// Get a handle on the `identity` unique index on the table `task_part`.
pub fn identity(&self) -> TaskPartIdentityUnique<'ctx> {
TaskPartIdentityUnique {
imp: self
.imp
.get_unique_constraint::<__sdk::Identity>("identity"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> TaskPartIdentityUnique<'ctx> {
/// Find the subscribed row whose `identity` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &__sdk::Identity) -> Option<TaskPart> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<TaskPart>("task_part");
_table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<TaskPart>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<TaskPart>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `TaskPart`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait task_partQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `TaskPart`.
fn task_part(&self) -> __sdk::__query_builder::Table<TaskPart>;
}
impl task_partQueryTableAccess for __sdk::QueryTableAccessor {
fn task_part(&self) -> __sdk::__query_builder::Table<TaskPart> {
__sdk::__query_builder::Table::new("task_part")
}
}

View File

@@ -1,60 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::action_type::Action;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct TaskPart {
pub identity: __sdk::Identity,
pub start_of_current: __sdk::Timestamp,
pub current: Action,
pub upcoming: Vec<Action>,
}
impl __sdk::InModule for TaskPart {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `TaskPart`.
///
/// Provides typed access to columns for query building.
pub struct TaskPartCols {
pub identity: __sdk::__query_builder::Col<TaskPart, __sdk::Identity>,
pub start_of_current: __sdk::__query_builder::Col<TaskPart, __sdk::Timestamp>,
pub current: __sdk::__query_builder::Col<TaskPart, Action>,
pub upcoming: __sdk::__query_builder::Col<TaskPart, Vec<Action>>,
}
impl __sdk::__query_builder::HasCols for TaskPart {
type Cols = TaskPartCols;
fn cols(table_name: &'static str) -> Self::Cols {
TaskPartCols {
identity: __sdk::__query_builder::Col::new(table_name, "identity"),
start_of_current: __sdk::__query_builder::Col::new(table_name, "start_of_current"),
current: __sdk::__query_builder::Col::new(table_name, "current"),
upcoming: __sdk::__query_builder::Col::new(table_name, "upcoming"),
}
}
}
/// Indexed column accessor struct for the table `TaskPart`.
///
/// Provides typed access to indexed columns for query building.
pub struct TaskPartIxCols {
pub identity: __sdk::__query_builder::IxCol<TaskPart, __sdk::Identity>,
}
impl __sdk::__query_builder::HasIxCols for TaskPart {
type IxCols = TaskPartIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
TaskPartIxCols {
identity: __sdk::__query_builder::IxCol::new(table_name, "identity"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for TaskPart {}

View File

@@ -1,58 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Task {
pub id: u64,
pub done: bool,
pub name: String,
pub parent: Option<u64>,
}
impl __sdk::InModule for Task {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Task`.
///
/// Provides typed access to columns for query building.
pub struct TaskCols {
pub id: __sdk::__query_builder::Col<Task, u64>,
pub done: __sdk::__query_builder::Col<Task, bool>,
pub name: __sdk::__query_builder::Col<Task, String>,
pub parent: __sdk::__query_builder::Col<Task, Option<u64>>,
}
impl __sdk::__query_builder::HasCols for Task {
type Cols = TaskCols;
fn cols(table_name: &'static str) -> Self::Cols {
TaskCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
done: __sdk::__query_builder::Col::new(table_name, "done"),
name: __sdk::__query_builder::Col::new(table_name, "name"),
parent: __sdk::__query_builder::Col::new(table_name, "parent"),
}
}
}
/// Indexed column accessor struct for the table `Task`.
///
/// Provides typed access to indexed columns for query building.
pub struct TaskIxCols {
pub id: __sdk::__query_builder::IxCol<Task, u64>,
}
impl __sdk::__query_builder::HasIxCols for Task {
type IxCols = TaskIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
TaskIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Task {}

View File

@@ -1,159 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::task_type::Task;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `tasks`.
///
/// Obtain a handle from the [`TasksTableAccess::tasks`] method on [`super::RemoteTables`],
/// like `ctx.db.tasks()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.tasks().on_insert(...)`.
pub struct TasksTableHandle<'ctx> {
imp: __sdk::TableHandle<Task>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `tasks`.
///
/// Implemented for [`super::RemoteTables`].
pub trait TasksTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`TasksTableHandle`], which mediates access to the table `tasks`.
fn tasks(&self) -> TasksTableHandle<'_>;
}
impl TasksTableAccess for super::RemoteTables {
fn tasks(&self) -> TasksTableHandle<'_> {
TasksTableHandle {
imp: self.imp.get_table::<Task>("tasks"),
ctx: std::marker::PhantomData,
}
}
}
pub struct TasksInsertCallbackId(__sdk::CallbackId);
pub struct TasksDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for TasksTableHandle<'ctx> {
type Row = Task;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Task> + '_ {
self.imp.iter()
}
type InsertCallbackId = TasksInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TasksInsertCallbackId {
TasksInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: TasksInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = TasksDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> TasksDeleteCallbackId {
TasksDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: TasksDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct TasksUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for TasksTableHandle<'ctx> {
type UpdateCallbackId = TasksUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> TasksUpdateCallbackId {
TasksUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: TasksUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `id` unique index on the table `tasks`,
/// which allows point queries on the field of the same name
/// via the [`TasksIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.tasks().id().find(...)`.
pub struct TasksIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Task, u64>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> TasksTableHandle<'ctx> {
/// Get a handle on the `id` unique index on the table `tasks`.
pub fn id(&self) -> TasksIdUnique<'ctx> {
TasksIdUnique {
imp: self.imp.get_unique_constraint::<u64>("id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> TasksIdUnique<'ctx> {
/// Find the subscribed row whose `id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &u64) -> Option<Task> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Task>("tasks");
_table.add_unique_constraint::<u64>("id", |row| &row.id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Task>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Task>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Task`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait tasksQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Task`.
fn tasks(&self) -> __sdk::__query_builder::Table<Task>;
}
impl tasksQueryTableAccess for __sdk::QueryTableAccessor {
fn tasks(&self) -> __sdk::__query_builder::Table<Task> {
__sdk::__query_builder::Table::new("tasks")
}
}

View File

@@ -1,44 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct TestArgs {}
impl __sdk::InModule for TestArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `test`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait test {
fn test(&self) {
self.test_then(|_, _| {});
}
fn test_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
);
}
impl test for super::RemoteProcedures {
fn test_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, String>("test", TestArgs {}, __callback);
}
}

View File

@@ -1,60 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Thought {
pub id: u64,
pub text: String,
pub user: __sdk::Identity,
pub time: __sdk::Timestamp,
}
impl __sdk::InModule for Thought {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Thought`.
///
/// Provides typed access to columns for query building.
pub struct ThoughtCols {
pub id: __sdk::__query_builder::Col<Thought, u64>,
pub text: __sdk::__query_builder::Col<Thought, String>,
pub user: __sdk::__query_builder::Col<Thought, __sdk::Identity>,
pub time: __sdk::__query_builder::Col<Thought, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for Thought {
type Cols = ThoughtCols;
fn cols(table_name: &'static str) -> Self::Cols {
ThoughtCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
text: __sdk::__query_builder::Col::new(table_name, "text"),
user: __sdk::__query_builder::Col::new(table_name, "user"),
time: __sdk::__query_builder::Col::new(table_name, "time"),
}
}
}
/// Indexed column accessor struct for the table `Thought`.
///
/// Provides typed access to indexed columns for query building.
pub struct ThoughtIxCols {
pub id: __sdk::__query_builder::IxCol<Thought, u64>,
pub time: __sdk::__query_builder::IxCol<Thought, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasIxCols for Thought {
type IxCols = ThoughtIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
ThoughtIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
time: __sdk::__query_builder::IxCol::new(table_name, "time"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Thought {}

View File

@@ -1,159 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::thought_type::Thought;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `thoughts`.
///
/// Obtain a handle from the [`ThoughtsTableAccess::thoughts`] method on [`super::RemoteTables`],
/// like `ctx.db.thoughts()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.thoughts().on_insert(...)`.
pub struct ThoughtsTableHandle<'ctx> {
imp: __sdk::TableHandle<Thought>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `thoughts`.
///
/// Implemented for [`super::RemoteTables`].
pub trait ThoughtsTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`ThoughtsTableHandle`], which mediates access to the table `thoughts`.
fn thoughts(&self) -> ThoughtsTableHandle<'_>;
}
impl ThoughtsTableAccess for super::RemoteTables {
fn thoughts(&self) -> ThoughtsTableHandle<'_> {
ThoughtsTableHandle {
imp: self.imp.get_table::<Thought>("thoughts"),
ctx: std::marker::PhantomData,
}
}
}
pub struct ThoughtsInsertCallbackId(__sdk::CallbackId);
pub struct ThoughtsDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for ThoughtsTableHandle<'ctx> {
type Row = Thought;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Thought> + '_ {
self.imp.iter()
}
type InsertCallbackId = ThoughtsInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ThoughtsInsertCallbackId {
ThoughtsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: ThoughtsInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = ThoughtsDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ThoughtsDeleteCallbackId {
ThoughtsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: ThoughtsDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct ThoughtsUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for ThoughtsTableHandle<'ctx> {
type UpdateCallbackId = ThoughtsUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> ThoughtsUpdateCallbackId {
ThoughtsUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: ThoughtsUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `id` unique index on the table `thoughts`,
/// which allows point queries on the field of the same name
/// via the [`ThoughtsIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.thoughts().id().find(...)`.
pub struct ThoughtsIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Thought, u64>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> ThoughtsTableHandle<'ctx> {
/// Get a handle on the `id` unique index on the table `thoughts`.
pub fn id(&self) -> ThoughtsIdUnique<'ctx> {
ThoughtsIdUnique {
imp: self.imp.get_unique_constraint::<u64>("id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> ThoughtsIdUnique<'ctx> {
/// Find the subscribed row whose `id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &u64) -> Option<Thought> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Thought>("thoughts");
_table.add_unique_constraint::<u64>("id", |row| &row.id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Thought>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Thought>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Thought`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait thoughtsQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Thought`.
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought>;
}
impl thoughtsQueryTableAccess for __sdk::QueryTableAccessor {
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought> {
__sdk::__query_builder::Table::new("thoughts")
}
}

View File

@@ -1,70 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::activity_type::Activity;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct UpdateActivityArgs {
pub activity: Activity,
}
impl From<UpdateActivityArgs> for super::Reducer {
fn from(args: UpdateActivityArgs) -> Self {
Self::UpdateActivity {
activity: args.activity,
}
}
}
impl __sdk::InModule for UpdateActivityArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `update_activity`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait update_activity {
/// Request that the remote module invoke the reducer `update_activity` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`update_activity:update_activity_then`] to run a callback after the reducer completes.
fn update_activity(&self, activity: Activity) -> __sdk::Result<()> {
self.update_activity_then(activity, |_, _| {})
}
/// Request that the remote module invoke the reducer `update_activity` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn update_activity_then(
&self,
activity: Activity,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl update_activity for super::RemoteReducers {
fn update_activity_then(
&self,
activity: Activity,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(UpdateActivityArgs { activity }, callback)
}
}

View File

@@ -1,70 +0,0 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::action_type::Action;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct UpdateUpcomingArgs {
pub upcoming: Vec<Action>,
}
impl From<UpdateUpcomingArgs> for super::Reducer {
fn from(args: UpdateUpcomingArgs) -> Self {
Self::UpdateUpcoming {
upcoming: args.upcoming,
}
}
}
impl __sdk::InModule for UpdateUpcomingArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `update_upcoming`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait update_upcoming {
/// Request that the remote module invoke the reducer `update_upcoming` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`update_upcoming:update_upcoming_then`] to run a callback after the reducer completes.
fn update_upcoming(&self, upcoming: Vec<Action>) -> __sdk::Result<()> {
self.update_upcoming_then(upcoming, |_, _| {})
}
/// Request that the remote module invoke the reducer `update_upcoming` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn update_upcoming_then(
&self,
upcoming: Vec<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl update_upcoming for super::RemoteReducers {
fn update_upcoming_then(
&self,
upcoming: Vec<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(UpdateUpcomingArgs { upcoming }, callback)
}
}

View File

@@ -1,21 +0,0 @@
{
"name": "@ppc/marts",
"version": "1.0.0",
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@flowershow/remark-wiki-link": "^3.4.0",
"builtin-modules": "^5.0.0",
"esbuild": "^0.27.3",
"mdast": "^3.0.0",
"obsidian": "^1.12.3",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"typescript": "^5.9.3",
"unified": "^11.0.5",
"unist-util-visit": "^5.1.0"
}
}

View File

@@ -1,16 +0,0 @@
[package]
name = "mize-spacetime"
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 = { version = "2.4.0", features = ["unstable"] }
log = "0.4"
chrono = { version = "0.4.45", features = [], default-features = false }
chrono-tz = { version = "0.10.4", default-features = false }
url = "2.5.8"

View File

@@ -1,394 +0,0 @@
use std::collections::HashMap;
use std::time::Duration;
use chrono::{DateTime, TimeZone};
use chrono_tz::Europe;
use log::*;
use spacetimedb::SpacetimeType;
use spacetimedb::sys::raw::index_id_from_name;
use spacetimedb::{Identity, ReducerContext, ProcedureContext, Table, Timestamp, reducer, table, procedure};
use spacetimedb::http::*;
#[table(accessor = profiles, public)]
pub struct Profile {
#[primary_key]
identity: Identity,
#[unique]
username: String,
}
#[table(accessor = task_part, public)]
pub struct TaskPart {
#[primary_key]
identity: Identity,
start_of_current: Timestamp,
current: Action,
upcoming: Vec<Action>,
}
#[table(accessor = activities, public)]
pub struct Activity {
#[primary_key]
#[auto_inc]
id: u64,
name: String,
task: Option<u64>,
parents_ids: Vec<u64>,
}
#[table(accessor = tasks, public)]
pub struct Task {
#[primary_key]
#[auto_inc]
id: u64,
done: bool,
name: String,
parent: Option<u64>,
}
#[table(accessor = thoughts, public)]
pub struct Thought {
#[primary_key]
#[auto_inc]
id: u64,
text: String,
user: Identity,
#[index(btree)]
time: Timestamp,
}
#[table(accessor = action)]
#[derive(Clone)]
pub struct Action {
name: String,
activity: Option<u64>,
task: Option<u64>,
start: Option<Timestamp>,
end: Option<Timestamp>,
duration: Option<u64>,
}
impl Action {
pub fn name_only(name: String) -> Self {
Action {
name,
activity: None,
task: None,
start: None,
end: None,
duration: None,
}
}
}
#[reducer]
pub fn add_thought(ctx: &ReducerContext, thought: String) -> Result<(), String> {
ctx.db.thoughts().try_insert(Thought {
id: 0,
text: thought,
user: ctx.sender(),
time: ctx.timestamp,
})?;
Ok(())
}
#[table(accessor = log_events, public, index(accessor = log_events_index, btree(columns = [user, end])))]
pub struct LogEvent {
#[primary_key]
#[auto_inc]
id: u64,
start: Timestamp,
end: Timestamp,
action: Action,
user: Identity,
}
#[reducer(init)]
pub fn init(_ctx: &ReducerContext) {
// Called when the module is initially published
info!("hiiiiiii from ppc module");
}
// the one from ppc, hardcoded rn, as we for now only support config at run time.
// this needs to be loaded at build time. (this code runs as a wasm module on the spacetimedb server)
const PPC_AUTH_CLIENT_ID: &str = "363331597203734531";
#[reducer(client_connected)]
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
check_auth(ctx)?;
info!("hooooo");
// add user
let _ = ctx.db.profiles().try_insert(Profile {
identity: ctx.sender(),
username: ctx.sender().to_string(),
});
Ok(())
}
fn check_auth(ctx: &ReducerContext) -> Result<(), String> {
let jwt = ctx
.sender_auth()
.jwt()
.ok_or("Authentication required".to_string())?;
if jwt.issuer() != "https://auth.ppc.social" {
return Err("Invalid issuer".to_string());
}
if !jwt.audience().iter().any(|a| a == PPC_AUTH_CLIENT_ID) {
return Err("Invalid audience".to_string());
}
Ok(())
}
#[reducer(client_disconnected)]
pub fn identity_disconnected(_ctx: &ReducerContext) {
// Called everytime a client disconnects
}
#[reducer]
pub fn add_activity(ctx: &ReducerContext, name: String) {
ctx.db.activities().insert(Activity {
name,
task: None,
id: 0, // auto-increments
parents_ids: Vec::new(),
});
}
#[reducer]
pub fn set_activity_name(ctx: &ReducerContext, id: u64, name: String) {
if let Some(old_ac) = ctx.db.activities().id().find(id) {
ctx.db.activities().id().update(Activity { name, ..old_ac });
} else {
return ();
}
}
#[reducer]
pub fn update_upcoming(ctx: &ReducerContext, upcoming: Vec<Action>) {
if ctx.db.task_part().identity().find(ctx.sender()).is_none() {
ctx.db.task_part().insert(TaskPart {
upcoming,
identity: ctx.sender(),
current: Action {
name: "filler".to_string(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
},
start_of_current: ctx.timestamp,
});
} else {
ctx.db.task_part().identity().update(TaskPart {
upcoming,
..ctx.db.task_part().identity().find(&ctx.sender()).unwrap()
});
}
}
#[reducer]
pub fn next_action(ctx: &ReducerContext, break_flag: bool, action: Option<Action>) -> Result<(), String> {
if let Some(mut task_part) = ctx.db.task_part().identity().find(&ctx.sender()) {
if task_part.upcoming.is_empty() {
return Err("No upcoming actions".to_string());
}
let new_action = match action {
Some(action) => action,
// when we do "ppc nextAction -b" ... we want to do a generic break "b" action.
None => if break_flag { Action::name_only("idk".to_string()) } else { task_part.upcoming.remove(0) },
};
// when break flag, push current_action again to the front of upcoming
if break_flag {
// insert generic "b" action when no one is specified
task_part.upcoming.insert(0, task_part.current.clone());
}
let start_of_old_action = task_part.start_of_current;
ctx.db.task_part().identity().update(TaskPart {
upcoming: task_part.upcoming,
identity: task_part.identity,
current: new_action.clone(),
start_of_current: ctx.timestamp,
});
// create log event
ctx.db.log_events().insert(LogEvent {
start: start_of_old_action,
end: ctx.timestamp,
action: task_part.current,
id: 0,
user: ctx.sender(),
});
Ok(())
} else {
Err("Task part data not found for this user".to_string())
}
}
#[reducer]
pub fn update_activity(ctx: &ReducerContext, activity: Activity) {
if activity.id == 0 {
ctx.db.activities().insert(activity);
} else {
ctx.db.activities().id().update(activity);
}
}
#[reducer]
pub fn remove_activity(ctx: &ReducerContext, activity: Activity) {
ctx.db.activities().delete(activity);
}
//////////////////// daily metric ///////////////////
#[table(accessor = daily_task, public)]
pub struct DailyTask {
id: i64,
name: String,
owner: Identity,
}
#[table(accessor = daily_task_log, public)]
pub struct DailyTaskLog {
daily_task_id: i64,
done: Timestamp,
}
#[procedure]
fn first_timetrack_date(ctx: &mut ProcedureContext, username: String) -> Result<String, String> {
let timestamp = ctx.try_with_tx(|tx| {
let profile = tx.db.profiles().username().find(username.clone()).ok_or("No such Profile".to_string())?;
let event = tx.db.log_events()
.log_events_index()
.filter(&profile.identity)
.nth(0)
.ok_or("Profile has no row in log_events")?;
let time = event.start
.to_chrono_date_time().map_err(|e| format!("chrono error: {e}"))?
.with_timezone(&chrono_tz::Europe::Vienna);
Ok::<String, String>(time.format("%Y-%m-%d").to_string())
})?;
Ok(timestamp)
}
#[procedure]
fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) -> Result<String, String> {
// CEST only rn
let start = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(5, 0, 0);
let start_local = chrono_tz::Europe::Vienna.from_local_datetime(&start).unwrap();
let start_utc = start_local.with_timezone(&chrono::Utc);
let start_timestamp = Timestamp::from_micros_since_unix_epoch(start_utc.timestamp_micros());
let end = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(4, 0, 0) + Duration::from_hours(24);
let end_local = chrono_tz::Europe::Vienna.from_local_datetime(&end).unwrap();
let end_utc = end_local.with_timezone(&chrono::Utc);
let end_timestamp = Timestamp::from_micros_since_unix_epoch(end_utc.timestamp_micros());
info!("start_of_day: {}", start_timestamp);
info!("end_of_day: {}", end_timestamp);
let mut export = String::new();
//export.push_str(&format!("### actions\n"));
let profile = ctx.with_tx(|tx| tx.db.profiles().username().find(&username)).ok_or(format!("Profile with username {} not found", username))?;
let events = ctx.with_tx(|tx|
tx.db.log_events()
.log_events_index()
.filter((profile.identity, start_timestamp..end_timestamp))
);
for event in events {
let start = event.start
.to_chrono_date_time()
.map_err(|err| err.to_string())?
.with_timezone(&Europe::Vienna)
.format("%H:%M");
let end = event.end
.to_chrono_date_time()
.map_err(|err| err.to_string())?
.with_timezone(&Europe::Vienna)
.format("%H:%M");
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
export.push_str(line.as_str());
}
return Ok(export); // don't do thoughts right now
export.push_str("\n");
export.push_str("## thoughts\n");
let thoughts = ctx.with_tx(|tx| {
tx.db.thoughts().time().filter(start_timestamp..end_timestamp)
});
for thought in thoughts {
let line = format!("- {}\n\n", thought.text);
export.push_str(line.as_str());
}
Ok(export)
}
#[procedure]
pub fn migrate(ctx: &mut ProcedureContext) -> String {
info!("migrate procedure called");
//let msg = "Nothing to migrate".to_string();
let msg: Result<String, String> = ctx.try_with_tx(|tx| {
let mut out = String::new();
let mut count = 0;
//for event in tx.db.log_events_tmp().iter() {
//count += 1;
//}
out.push_str(format!("Migrated {} rows................", count).as_str());
Ok(out)
});
return match msg {
Ok(msg) => msg,
Err(err) => err,
};
}
#[procedure]
pub fn test(ctx: &mut ProcedureContext) -> String {
let mut s = String::from("hoooooooooooooooooooooo\n");
let a: String = ctx.with_tx(|_tx| {
let s = String::new();
return s;
});
s.push_str(&format!("{}\n\n", a));
s.push_str("\n");
s.push_str(&format!("sender: {}", ctx.sender()));
s
}
#[handler]
pub fn test_handler(ctx: &mut HandlerContext, _req: Request<Body>) -> Response<Body> {
let res = ctx.try_with_tx(|tx| {
let mut s = String::new();
for a in tx.db.profiles().iter() {
s.push_str(&format!("{} ... {}\n", a.identity, a.username));
}
s.push_str(&format!("sender: {}", tx.sender()));
return Ok(s);
});
return Response::new(Body::from_bytes(match res {
Ok(body) => body,
Err(e) => e,
}));
}
#[spacetimedb::http::router]
fn router() -> Router {
Router::new()
.get("/test", test_handler)
}

View File

@@ -1,677 +0,0 @@
use chrono::{DateTime, TimeDelta, Utc};
use mize::out;
use mize::{
item::ItemData, mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult,
};
use openidconnect::{
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::{path::PathBuf, thread};
use tracing::{info, warn};
// console_log macro
// that can be copied into other files for debugging purposes
#[cfg(feature = "target-obsidian")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "target-obsidian")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[cfg(feature = "target-obsidian")]
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
}
#[cfg(feature = "target-os")]
use clap::Command;
#[derive(Default)]
#[mize_part]
pub struct ClientAuth {
mize: Mize,
secrets: Option<ClientSecrets>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ClientSecrets {
id_token: String,
access_token: String,
refresh_token: String,
expiry: DateTime<Utc>,
}
impl MizePartCreate for ClientAuth {}
impl MizePart for ClientAuth {
fn name(&self) -> &'static str {
"client_auth"
}
}
#[cfg(feature = "target-js")]
pub fn store_secrets_js(mize: &Mize, secrets: &ClientSecrets) -> MizeResult<()> {
use web_sys::window;
// Serialize struct to JSON string
let serialized = serde_json::to_string(&secrets)?;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Store the JSON string
storage.set_item("mize_client_secrets", &serialized).map_err(|_| mize_err!("couldn't set item in local_storage"))?;
Ok(())
}
impl ClientAuth {
pub fn secrets(&self) -> Option<&ClientSecrets> {
self.secrets.as_ref()
}
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 refresh(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-js")] {
console_log!("refresh is not implemented yet on wasm");
}
#[cfg(feature = "target-os")] {
let secrets = self.secrets.clone().ok_or(mize_err!(
"no client secrets, meaning client_auth.refresh() thread can't use the refresh token"
))?;
let new_secrets = self.mize.spawn_async_blocking(
"auth.refresh_flow",
refresh_flow(self.mize.clone(), secrets),
)?;
store_secrets(&mut self.mize, &new_secrets)?;
self.secrets = Some(new_secrets);
}
Ok(())
}
pub fn login(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
if self.secrets().is_none() {
let secrets = self
.mize
.spawn_async_blocking("auth.login_flow", auth_flow(self.mize.clone()))?;
store_secrets(&mut self.mize, &secrets)?;
self.secrets = Some(secrets);
}
}
#[cfg(feature = "target-js")] {
if self.secrets().is_none() {
let mut mize_clone = self.mize.clone();
wasm_bindgen_futures::spawn_local(async move {
console_log!("before auth_flow");
let secrets = match auth_flow(mize_clone.clone()).await {
Ok(secrets) => secrets,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
console_log!("after auth_flow");
if let Err(e) = store_secrets_js(&mut mize_clone, &secrets) {
mize_clone.report_err(e);
return ();
}
let mut auth = match mize_clone.get_part_native::<ClientAuth>("client_auth") {
Ok(auth) => auth,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
auth.secrets = Some(secrets);
});
}
}
Ok(())
}
pub fn start_refresh_thread(&mut self) -> MizeResult<()> {
// background thread for os targets
#[cfg(not(feature = "target-js"))]
{
let mut mize_clone = self.mize.clone();
self.mize.spawn_background("auth.refresh", move || loop {
info!("hi from auth.refresh thread...");
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
let auth = mize_clone.get_part_native::<ClientAuth>("client_auth")?;
let expiry = auth.secrets.as_ref().unwrap().expiry.clone();
info!("auth.refresh thread: now: {}", Utc::now());
info!("auth.refresh thread: expiry: {}", expiry);
let to_sleep = expiry - Utc::now() - TimeDelta::minutes(20);
/*
if to_sleep < TimeDelta::from_std(Duration::from_secs(0))? {
if let Err(e) = auth.refresh() {
e.log();
}
}
*/
drop(auth);
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
thread::sleep(to_sleep.to_std()?);
info!("refresh_thread: sleeping done");
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
drop(auth);
})?;
Ok(())
}
// spawn an async background task doing the refresh
#[cfg(feature = "target-js")] {
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
// Get the window object
let window = web_sys::window().expect("no global `window` exists");
// Clone mize for the closure
let mize_clone = self.mize.clone();
// Create a closure that will be called periodically
let refresh_closure = Closure::wrap(Box::new(move || {
let mut mize_clone = mize_clone.clone();
wasm_bindgen_futures::spawn_local(async move {
let mut auth = mize_clone.get_part_native::<ClientAuth>("client_auth");
match auth {
Ok(mut auth_part) => {
if let Some(secrets) = auth_part.secrets() {
let now = Utc::now();
let expiry = secrets.expiry;
let time_until_expiry = expiry - now;
// Refresh if token will expire in less than 20 minutes
if time_until_expiry < TimeDelta::minutes(20) {
if let Err(e) = auth_part.refresh() {
console_log!("Error refreshing token: {:?}", e);
} else {
console_log!("Token refreshed successfully");
}
}
}
}
Err(e) => {
console_log!("Error getting auth part: {:?}", e);
}
}
});
}) as Box<dyn FnMut()>);
// Set up interval to run every 50 minutes (300000 ms)
let interval_ms = 3000000;
let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
refresh_closure.as_ref().unchecked_ref(),
interval_ms,
);
// Leak the closure to prevent it from being dropped
refresh_closure.forget();
Ok(())
}
}
}
pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> {
let mize_clone = mize.clone();
let mut auth_part = ClientAuth {
secrets: None,
mize: mize.clone(),
};
let secrets = match load_secrets(mize) {
Ok(secrets) => Some(secrets),
Err(err) => {
tracing::warn!("err reading secrets: {:?}", err);
None
}
};
auth_part.secrets = secrets.clone();
// do a refresh if the tokens have expired
if secrets.is_some() && secrets.clone().unwrap().expiry < Utc::now() {
auth_part.refresh()?;
}
// start refresh tread if configured
if mize
.get_config("auth.client.refresh_thread")
.unwrap_or(ItemData::new())
.value_bool()
.unwrap_or(false)
{
auth_part.start_refresh_thread()?;
}
// cli command for os
#[cfg(feature = "target-os")] {
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("ppc");
cli.subcommand(Command::new("login"), move |_, mut mize| {
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
let secrets = if auth.secrets.is_some() {
match mize.spawn_async_blocking(
"client_auth",
refresh_flow(mize.clone(), auth.secrets.clone().unwrap()),
) {
Ok(secrets) => secrets,
Err(err) => {
out!("err refreshing secrets: {:?}", err);
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
}
}
} else {
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
};
store_secrets(&mut mize, &secrets)?;
auth.secrets = Some(secrets);
Ok(())
})?;
}
// register command for obsidian
#[cfg(feature = "target-obsidian")] {
use crate::MartsCommand;
console_log!("register obsidian command...");
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
obsidian.add_command(MartsCommand {
id: "mize-login".to_string(),
name: "Mize Login".to_string(),
callback: Box::new(|mut mize| {
let mut auth = mize.get_part_native::<crate::ClientAuth>("client_auth")?;
let res = auth.login();
console_log!("auth.login done: {:?}", res);
res
}),
})?;
}
auth_part.secrets = secrets;
mize.register_part(Box::new(auth_part))?;
Ok(())
}
fn load_secrets(mize: &mut Mize) -> MizeResult<ClientSecrets> {
#[cfg(feature = "target-os")] {
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)?;
return Ok(secrets);
}
#[cfg(feature = "target-js")] {
use web_sys::window;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Retrieve the string item
return match storage.get_item("mize_client_secrets").map_err(|_| mize_err!("couldn't get item from local_storage"))? {
Some(json_str) => {
// Deserialize JSON string back into Client struct
let secrets: ClientSecrets = serde_json::from_str(json_str.as_str())?;
Ok(secrets)
}
None => Err(mize_err!("no secrets")), // Key does not exist
}
}
}
fn store_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
let mize_dir = mize.get_config("store")?.value_string()?;
let secrets_file_path = PathBuf::from(mize_dir.clone())
.join("misc")
.join("client_secrets.json");
std::fs::create_dir_all(PathBuf::from(mize_dir).join("misc"))?;
let secrets_string = serde_json::to_string(secrets)?;
log::info!("secrets_file_path: {}", secrets_file_path.display());
//std::fs::write(secrets_file_path, secrets_string)?;
OpenOptions::new()
.write(true)
.create(true)
.open(secrets_file_path)?
.write_all(&secrets_string.as_bytes())?;
// update the spacetimedb_token in XDG_CONFIG_HOME/spacetime/cli.toml config file if configured to do so
if mize
.get_config("auth.client.update_spacetime_token")
.unwrap_or(ItemData::empty())
.as_bool()
.unwrap_or(false)
{
update_spacetimedb_token(&secrets.id_token.as_str())?;
}
}
Ok(())
}
use openidconnect::reqwest::Client as HttpClient;
use openidconnect::DeviceAuthorizationUrl;
use std::time::Duration;
//use wasm_bindgen::JsCast;
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
out!("Refresh flow...");
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
let refresh_token = RefreshToken::new(old_secrets.refresh_token);
let res = client
.exchange_refresh_token(&refresh_token)?
.request_async(&http_client)
.await?;
let expires_in = res.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
return Ok(ClientSecrets {
id_token: res.id_token().unwrap().to_string(),
access_token: res.access_token().secret().to_owned(),
refresh_token: res.refresh_token().unwrap().secret().to_owned(),
expiry: Utc::now() + expires_in,
});
}
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
// ===== DEVICE AUTH REQUEST =====
let details: Result<DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>, _> =
client
.exchange_device_code()
.add_scope(Scope::new("openid".into()))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("offline_access".into()))
.request_async(&http_client)
.await;
let details = match details {
Ok(val) => val,
Err(err) => {
out!("err: {:?}", err);
out!("err: {}", err);
panic!();
}
};
#[cfg(feature = "target-obsidian")] {
console_log!("details: {:?}", details);
use std::str::FromStr;
use js_sys::{Array, Function, JsString, Reflect};
use mize::error::IntoMizeResultJsValue;
use wasm_bindgen::convert::TryFromJsValue;
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
let show_device_code = Reflect::get(&obsidian.plugin(), &JsValue::from_str("showDeviceCode")).mres()?;
let args = Array::new();
args.push(&JsValue::from_str(details.user_code().secret().as_str()));
args.push(&JsValue::from_str(details.verification_uri_complete().unwrap().secret().as_str()));
Reflect::apply(&show_device_code.dyn_into::<Function>().mres()?, obsidian.plugin(), &args).mres()?;
}
#[cfg(feature = "target-os")] {
out!(
"If your browser does not open automatically, open this URL in it:\n{}\n",
details.verification_uri_complete().unwrap().secret()
);
out!(
"Or enter this PPC login code:\n{}\n",
details.user_code().secret()
);
if let Some(_) = details.verification_uri_complete() {
let _ = webbrowser::open(details.verification_uri_complete().unwrap().secret());
}
}
let token_response = client
.exchange_device_access_token(&details)?
.request_async(
&http_client,
async |dur: Duration| {
#[cfg(feature = "target-os")] {
tokio::time::sleep(dur).await;
}
#[cfg(feature = "target-js")] {
console_log!("sleeping");
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
let promise = js_sys::Promise::new(&mut |resolve, _| {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, dur.as_millis() as i32)
.unwrap();
});
// Await the JS Promise like a Rust Future
let _ = JsFuture::from(promise).await;
}
},
None,
)
.await?;
// ===== TOKENS =====
let id_token = token_response
.extra_fields()
.id_token()
.ok_or(mize_err!("no id_token"))?;
let refresh = token_response
.refresh_token()
.ok_or(mize_err!("no refresh token present"))?;
let expires_in = token_response.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
Ok(ClientSecrets {
id_token: id_token.to_string(),
access_token: token_response.access_token().secret().to_string(),
refresh_token: refresh.secret().to_owned(),
expiry: Utc::now() + expires_in,
})
}
fn update_spacetimedb_token(new_token: &str) -> MizeResult<()> {
// Resolve config directory
let config_dir = match std::env::var("XDG_CONFIG_HOME") {
Ok(val) => PathBuf::from(val),
Err(_) => {
let home = std::env::var("HOME")?;
std::path::PathBuf::from(home).join(".config")
}
};
let file_path = config_dir.join("spacetime").join("cli.toml");
if !file_path.exists() {
warn!("spacetime config file does not exist, not updating token there");
return Ok(());
};
// Read existing file (or create empty document if it doesn't exist)
let content = std::fs::read_to_string(&file_path).unwrap_or_default();
let mut data = ItemData::from_toml(content.as_str())?;
// Update the field
data.set_path("spacetimedb_token", new_token)?;
// Write back
std::fs::write(&file_path, data.to_toml()?)?;
Ok(())
}

View File

@@ -1,9 +0,0 @@
use dioxus::prelude::*;
pub fn main() -> Element {
rsx! {
div {
h1 { "my dashbaord" }
}
}
}

View File

@@ -1,431 +0,0 @@
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { eq } from "drizzle-orm";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { spawnSync } from "node:child_process";
export async function init(ppc: PPC) {
ppc.c2vi = new C2vi(ppc);
ppc.cli
.command("bed", "command to run when c2vi goes to bed")
.action(async () => {
await save_habitica_dailies(ppc);
await save_habitica_logs(ppc);
});
ppc.cli
.command("clearTodos", "command to run when c2vi goes to bed")
.alias("clt")
.action(async () => {
const todos = await ppc.habitica.get_tasks();
for (const todo of todos) {
await ppc.habitica.delete_task(todo.id);
}
});
ppc.cli
.command("listTodos", "command to run when c2vi goes to bed")
.alias("dut")
.action(async () => {
const todos = await ppc.habitica.get_tasks();
for (const todo of todos) {
console.log(todo.text);
}
});
ppc.cli
.command("buyHealthPotion", "command to run when c2vi goes to bed")
.alias("buyh")
.action(async () => {
await ppc.habitica.api_request("POST", "/user/buy-health-potion");
});
ppc.cli
.command("addActionItems", "command to run when c2vi goes to bed")
.alias("aal")
.action(async () => {
await add_action_items(ppc);
});
ppc.cli
.command("test", "a command to run some test code")
.alias("t")
.action(async () => {});
ppc.cli
.command("skip [num]", "command to skip a habitica todo")
.alias("sk")
.action((num = 1) => {
skip_habitica_todo(ppc, parseInt(num));
});
ppc.cli.command("dumpLog", "dump the complete habitica log").action(() => {
dump_habitica_logs(ppc);
});
ppc.cli
.command("listDailies", "list all dailies")
.action(async (folder, options) => {
const allDailies = await ppc.c2vi.db.select().from(dailiesTable).all();
for (const task of allDailies) {
console.log(task.id + ": " + task.text);
}
});
ppc.cli
.command("printTask <id>", "print a specific todo")
.action(async (id) => {
const data = await ppc.habitica.api_request("GET", `/tasks/${id}`, {});
console.log(data.history);
});
}
class C2vi {
[key: string]: any;
constructor(ppc: PPC) {
this.ppc = ppc;
this.db = drizzle(
createClient({
url: "file://" + ppc.config.local_storage_path + "/data.db",
}),
);
}
}
function formatDate(d) {
const pad = (n) => n.toString().padStart(2, "0");
// Custom YYYY-MM-DD format
const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
return formatted;
}
// ============= SCHEMA DEFINITION =============
const dailiesTable = sqliteTable("habitica_dailies", {
id: text("id").primaryKey(),
text: text("text").notNull(),
notes: text("notes"),
priority: text("priority"), // 0.1, 1, 1.5, 2
frequency: text("frequency"), // 'daily', 'weekly', etc.
});
const habiticaLogTable = sqliteTable("habitica_log", {
date: text("date").primaryKey(),
data: text("data").notNull(),
});
async function save_habitica_dailies(ppc) {
console.log("######### cli bed cmd #########");
await ppc.c2vi.db.run(
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
);
await ppc.c2vi.db.run(
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
);
// ============= DATABASE INITIALIZATION =============
if (!ppc.config.habitica.user_id || !ppc.config.habitica.api_token) {
console.error(
"Error: HABITICA_USER_ID and HABITICA_API_TOKEN env vars required",
);
console.error(
"Get these from Habitica Settings > API and configure them in the ppc.config",
);
process.exit(1);
}
const dailies = await ppc.habitica.get_tasks("dailys");
await writeDailiesToDatabase(dailies, ppc);
}
async function dump_habitica_logs(ppc) {
const entries = await ppc.c2vi.db.select().from(habiticaLogTable).all();
for (const entry of entries) {
console.log(entry.date + ": ", JSON.parse(entry.data));
}
}
async function skip_habitica_todo(ppc, num) {
await ppc.c2vi.db.run(
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
);
await ppc.c2vi.db.run(
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
);
const todos = await ppc.habitica.get_tasks("todos");
const date = new Date();
const save_for_yesterday = date.getHours() < 12;
if (save_for_yesterday) {
console.log(
"Saving logs for yesterday!!!! because it is " +
date.getHours() +
"hours of the day",
);
date.setDate(date.getDate() - 1);
}
const result = await ppc.c2vi.db
.select()
.from(habiticaLogTable)
.where(eq(habiticaLogTable.date, formatDate(date)));
const data = result[0]
? JSON.parse(result[0].data)
: {
dailies_done: [],
dailies_skipped: [],
todos_done: [],
todos_skipped: [],
};
for (let i = 0; i < num; i++) {
const todo = todos[i];
if (!todo) continue;
await ppc.habitica.delete_task(todo.id);
await incrementHabit(ppc);
data.todos_skipped.push({
id: todo.id,
text: todo.text,
notes: todo.notes || "",
checklist: todo.checklist || [],
tags: todo.tags || [],
});
}
// saving to db
await ppc.c2vi.db
.insert(habiticaLogTable)
.values({ date: formatDate(date), data: JSON.stringify(data) })
.onConflictDoUpdate({
target: habiticaLogTable.date,
set: {
data: JSON.stringify(data),
},
});
}
async function save_habitica_logs(ppc) {
const date = new Date();
const save_for_yesterday = date.getHours() < 12;
if (save_for_yesterday) {
console.log(
"Saving logs for yesterday!!!! because it is " +
date.getHours() +
"hours of the day",
);
date.setDate(date.getDate() - 1);
}
const result = await ppc.c2vi.db
.select()
.from(habiticaLogTable)
.where(eq(habiticaLogTable.date, formatDate(date)));
const data = result[0]
? JSON.parse(result[0].data)
: {
dailies_done: [],
dailies_skipped: [],
todos_done: [],
todos_skipped: [],
};
// dailies
const dailies = await ppc.habitica.get_tasks("dailys");
for (const daily of dailies) {
let completed = daily.completed;
let isDue = daily.isDue;
if (save_for_yesterday) {
const yesterday = daily.history.reduce((prev, current) => {
return prev.date > current.date ? prev : current;
});
completed = yesterday.completed;
isDue = yesterday.isDue;
}
if (!isDue) {
continue;
}
if (completed) {
data.dailies_done.push(daily.id);
console.log("daily-done: " + daily.text);
} else {
data.dailies_skipped.push(daily.id);
console.log("daily-skipped: " + daily.text);
}
}
// todos done
const todos = await ppc.habitica.get_tasks("completedTodos");
for (const todo of todos) {
const dateCompleted = new Date(todo.dateCompleted);
// check if this todo is already saved in yesterday's log entry
if (save_for_yesterday) {
// get yesterday log entry
const result = await ppc.c2vi.db
.select()
.from(habiticaLogTable)
.where(eq(habiticaLogTable.date, formatDate(date)));
const data = result[0]
? JSON.parse(result[0].data)
: {
dailies_done: [],
dailies_skipped: [],
todos_done: [],
todos_skipped: [],
};
// check if this todo is already saved in yesterday's log entry
const todoIndex = data.todos_done.findIndex((t) => t.id === todo.id);
if (todoIndex !== -1) {
continue; // skipp saving this todo
}
}
if (dateCompleted.toDateString() == date.toDateString()) {
console.log("todo-done:", todo.text);
data.todos_done.push({
id: todo.id,
text: todo.text,
notes: todo.notes || "",
checklist: todo.checklist || [],
tags: todo.tags || [],
});
}
// we save_for_yesterday but the todo was completed today... also save that for this day
if (
save_for_yesterday &&
dateCompleted.toDateString() == new Date().toDateString()
) {
console.log("todo-done:", todo.text);
data.todos_done.push({
id: todo.id,
text: todo.text,
notes: todo.notes || "",
checklist: todo.checklist || [],
tags: todo.tags || [],
});
}
}
// saving to db
await ppc.c2vi.db
.insert(habiticaLogTable)
.values({ date: formatDate(date), data: JSON.stringify(data) })
.onConflictDoUpdate({
target: habiticaLogTable.date,
set: {
data: JSON.stringify(data),
},
});
}
async function writeDailiesToDatabase(dailies, ppc) {
console.log(`Syncing ${dailies.length} dailies to database...`);
const records = dailies.map((daily) => ({
id: daily.id,
text: daily.text,
notes: daily.notes || "",
priority: daily.priority?.toString() || null,
frequency: daily.frequency || "daily",
}));
async function upsertMany(records) {
for (const record of records) {
await ppc.c2vi.db
.insert(dailiesTable)
.values(record)
.onConflictDoUpdate({
target: dailiesTable.id,
set: {
text: record.text,
notes: record.notes,
priority: record.priority,
frequency: record.frequency,
},
});
}
}
await upsertMany(records);
}
async function incrementHabit(ppc) {
const habits = await ppc.habitica.get_tasks("habits");
let id = "";
for (const habit of habits) {
if ((habit.text = "skipped a planned task")) {
id = habit.id;
}
}
await ppc.habitica.api_request("POST", `tasks/${id}/score/down`);
}
async function add_action_items(ppc: PPC) {
let task_ids = [];
const tmpFile = path.join(os.tmpdir(), `habitica-tasks-${Date.now()}.md`);
const initialContent = `
gu 10min TIMER
uw 15min FROG
b coffee/tee/kakau
uw FROG
b breakfast, brush teeth, lüften
uw FROG
lunch
uw 15min TIMER of next days FROG
fw
dt 30min TIMER
fun 20:00 (smth that can be stopped easily)
se 21:40
read
bed 22:10
`;
fs.writeFileSync(tmpFile, initialContent.trim());
spawnSync("nvim", [tmpFile], { stdio: "inherit" });
// read file
const listStr = fs.readFileSync(tmpFile, "utf8");
// delete file
fs.unlinkSync(tmpFile);
// Simple parser: filter out empty lines
const tasks = listStr
.split("\n")
.map((t) => t.trim())
.filter((t) => t.length > 0);
// 5. Add tasks to Habitica
for (const todoText of tasks) {
const data = await ppc.habitica.api_request(
"POST",
"tasks/user",
{},
{ text: todoText, type: "todo" },
);
task_ids.push(data.id);
console.log(`Added to-do: '${todoText}' with id '${data.id}'`);
}
// 6. Move tasks to bottom
for (const taskId of task_ids) {
await ppc.habitica.api_request("POST", `tasks/${taskId}/move/to/-1`);
}
}

View File

@@ -1,170 +0,0 @@
export function c2vi_obsidian_canvas_patch(mize: any) {
const plugin = mize.get_part_native("obsidian");
window.pc = patchCanvas;
window.upc = () => {
unpatchCanvas(plugin, plugin.app.workspace.activeLeaf);
};
console.log("hereeeeeeeeeeeeeeeee");
// canvas patching
plugin.registerEvent(
plugin.app.workspace.on("file-open", (file: any) => {
if (file?.extension === "canvas") {
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
}
}),
);
plugin.registerEvent(
plugin.app.workspace.on("active-leaf-change", (file: any) => {
if (file?.extension === "canvas") {
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
}
}),
);
}
const maybePathCanvas = (plugin: PPCObsidianPlugin, leaf: any | null) => {
console.log("maybePathCanvas called");
const file = plugin.app.workspace.getActiveFile();
if (file?.extension !== "canvas") return;
console.log("patching canvas.....");
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
if ("isPatched" in canvas) return;
patchCanvas(plugin, leaf, canvas);
};
function patchCanvas(plugin: PPCObsidianPlugin, leaf: any, canvas: any) {
canvas.isPatched = true;
const canvasEl = canvas.canvasEl;
const canvasWrapperEl = canvasEl.parentElement;
const overlay = createDiv();
overlay.setAttr("class", "c2vi-canvas-overlay");
const canvas_card_menue = canvasWrapperEl.childNodes[1];
canvasWrapperEl.insertBefore(overlay, canvas_card_menue);
Object.assign(overlay.style, {
position: "absolute",
inset: "0",
//zIndex: "1337",
});
overlay.addEventListener("contextmenu", (e) => {
if (e.button === 2) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
overlay.addEventListener("mouseout", (e) => {
const menu = document.getElementsByClassName("menu")[0];
if (!menu) return;
menu.addEventListener("pointerup", (e) => {
console.log("pointerup on menu", e);
e.target.click();
/*
menu.dispatchEvent(
new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
composed: true,
clientX: e.clientX,
clientY: e.clientY,
button: e.button,
buttons: e.buttons,
view: window,
}),
);
*/
});
});
setupEventPropagation("pointerdown", PointerEvent, overlay, canvasWrapperEl);
setupEventPropagation("pointerup", PointerEvent, overlay, canvasWrapperEl);
setupEventPropagation("mousedown", MouseEvent, overlay, canvasWrapperEl);
setupEventPropagation("mouseup", MouseEvent, overlay, canvasWrapperEl);
setupEventPropagation("click", MouseEvent, overlay, canvasWrapperEl);
setupEventPropagation("dragstart", DragEvent, overlay, canvasWrapperEl);
setupEventPropagation("dragstop", DragEvent, overlay, canvasWrapperEl);
window.a = canvasWrapperEl;
window.b = overlay;
const mouseEvents = [
"mousedown",
"mouseup",
"click",
"dblclick",
//"mousemove",
"mouseover",
"mouseout",
"wheel",
];
mouseEvents.forEach((eventType) => {
canvasWrapperEl.addEventListener(
eventType,
(event) => {
console.log(`Mouse Event Triggered: ${event.type}`, event);
},
{ passive: true },
);
});
}
function unpatchCanvas(plugin: PPCObsidianPlugin, leaf: any) {
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
if (!canvas.isPatched) return;
canvas.isPatched = false;
const canvasEl = canvas.canvasEl;
const canvasWrapperEl = canvasEl.parentElement;
const targets = canvasEl.querySelectorAll(":scope > .c2vi-canvas-overlay");
targets.forEach((child) => child.remove());
}
function setupEventPropagation(type: string, evClass, overlay: any, target) {
overlay.addEventListener(type, (originalEvent) => {
originalEvent.stopPropagation();
originalEvent.stopImmediatePropagation();
originalEvent.preventDefault();
const newEvent = new evClass(type, originalEvent);
if (type == "pointerdown" && originalEvent.ctrlKey == true) {
dispatchContextMenue(
target,
originalEvent.clientX,
originalEvent.clientY,
);
return;
}
if (type === "pointerdown") {
window.downEv = newEvent;
}
if (type === "pointerup") {
window.upEv = newEvent;
}
newEvent.isTrusted = true;
console.log("original Event of", type, originalEvent);
console.log("new Event of", type, newEvent);
target.dispatchEvent(newEvent);
});
}
function dispatchContextMenue(target, x, y) {
const newEvent = new PointerEvent("contextmenu", {
bubbles: true,
cancelable: true,
composed: true,
view: window,
clientX: x,
clientY: y,
pointerType: "mouse",
button: 2,
buttons: 2,
pointerId: 1,
isPrimary: true,
});
target.dispatchEvent(newEvent);
}

View File

@@ -1,995 +0,0 @@
use std::sync::Mutex;
use chrono::{Timelike, Utc};
use clap::{Arg, Command};
#[cfg(feature = "desktop")]
use mize::item::ItemData;
use rusqlite::{params, Connection};
use serde_json::{json, Value};
use clap::ArgMatches;
use cmd_lib::run_cmd as sh;
use dioxus::prelude::*;
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
use tracing::instrument::WithSubscriber;
use crate::cli::CliPart;
use crate::habitica::Habitica;
use crate::SpacetimePart;
use generated::spacetime::{
Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming
};
use spacetimedb_sdk::{
credentials, DbContext, Error, Event, Identity, Status,
TableWithPrimaryKey,
};
static MIZE_SRC: &str = "/home/me/work/config/mize";
static SRC: &str = "/home/me/work/config";
static RSYNC_EXCLUDES: &str = "--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg --exclude /src --exclude obsidian-brat --exclude forked-zed";
#[mize_part]
#[derive(Default)]
pub struct C2vi {
mize: Mize,
db: Mutex<Option<Connection>>,
}
impl MizePart for C2vi {
fn opts(&self, mize: &mut Mize) {
mize.new_opt("c2vi.local_storage_path");
}
}
impl MizePartCreate for C2vi {}
impl C2vi {
fn with_db<F, R>(&self, f: F) -> MizeResult<R>
where
F: FnOnce(&Connection) -> MizeResult<R>,
{
let guard = self
.db
.lock()
.map_err(|e| mize_err!("DB lock poisoned: {}", e))?;
let conn = guard
.as_ref()
.ok_or_else(|| mize_err!("DB not initialized"))?;
f(conn)
}
}
pub fn part_c2vi(mize: &mut Mize) -> MizeResult<()> {
c2vi_deploy_things(mize)?;
#[cfg(feature = "desktop")]
if mize
.get_config("c2vi.dash.enable")
.unwrap_or(ItemData::empty())
.as_bool()
.unwrap_or(false)
{
launch_c2vi_dashboard(mize);
}
#[cfg(feature = "target-obsidian")] {
use crate::MartsCommand;
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian").unwrap();
obsidian.add_command(MartsCommand {
id: "sc2-publish-daily-journal".to_string(),
name: "SC2 Publish Daily Journal".to_string(),
callback: Box::new(|mut mize| {
let matrix = mize.get_part_native::<crate::matrix::MatrixPart>("matrix")?;
//matrix.send_message(room_id);
Ok(())
}),
})?;
}
Ok(())
}
pub fn new_deploy(mize: Mize, matches: &ArgMatches, hostname: &str) -> MizeResult<()> {
let path = match hostname {
"mac" => "/home/me/here/mize",
_ => "/home/me/here/mize",
};
let is_release = match hostname {
"mac" => true,
_ => false,
};
let ld_lib_path = std::env::var("LD_LIBRARY_PATH")?;
if is_release {
sh!(echo cmd: cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize --release)?;
sh!(cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize --release)?;
sh!(echo cmd: patchelf --set-rpath $ld_lib_path $SRC/mize/target/release/mize)?;
let _ = sh!(patchelf --set-rpath $ld_lib_path $SRC/mize/target/release/mize);
sh!(echo cmd: scp $SRC/mize/target/release/mize $hostname:$path)?;
sh!(scp $SRC/mize/target/release/mize $hostname:$path)?;
} else {
sh!(echo cmd: cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize)?;
sh!(cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize)?;
sh!(echo cmd: patchelf --set-rpath $ld_lib_path $SRC/mize/target/debug/mize)?;
let _ = sh!(patchelf --set-rpath $ld_lib_path $SRC/mize/target/debug/mize);
sh!(echo cmd: scp $SRC/mize/target/debug/mize $hostname:$path)?;
sh!(scp $SRC/mize/target/debug/mize $hostname:$path)?;
}
Ok(())
}
fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<CliPart>("cli")?;
cli.set_command("dev");
let bin_arg = Arg::new("bin").short('b');
cli.subcommand(
Command::new("deploy-lush").alias("dlu").arg(&bin_arg),
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "te")),
)?;
cli.subcommand(
Command::new("deploy-mac").alias("dmc").arg(&bin_arg),
Box::new(move |matches: &ArgMatches, mize| new_deploy(mize, matches, "mac")),
)?;
cli.subcommand(
Command::new("deploy-files").alias("dfi").arg(&bin_arg),
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "files")),
)?;
cli.subcommand(
Command::new("deploy-ppc").alias("dpp").arg(&bin_arg),
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "ppc-hosting")),
)?;
cli.subcommand(
Command::new("deploy-nico").alias("dni").arg(&bin_arg),
Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "nico")),
)?;
cli.subcommand(
Command::new("deploy-main").alias("dma").arg(&bin_arg),
Box::new(main_deploy),
)?;
cli.subcommand(
Command::new("deploy-obsidian").alias("do").arg(&bin_arg),
Box::new(obsidian_deploy),
)?;
cli.subcommand(
Command::new("deploy-spacetime").alias("dsp").arg(&bin_arg),
Box::new(deploy_spacetime),
)?;
Ok(())
}
#[cfg(feature = "desktop")]
pub fn launch_c2vi_dashboard(mize: &mut Mize) {
std::env::remove_var("WAYLAND_DISPLAY");
std::env::set_var("DISPLAY", ":0");
use dioxus::desktop::{muda::Menu, Config, WindowBuilder};
let mize_clone = mize.clone();
LaunchBuilder::new()
.with_context_provider(move || Box::new(mize_clone.clone()))
.with_cfg(desktop! {
Config::new().with_window(WindowBuilder::new().with_decorations(false).with_title("SCI display"))
})
.launch(c2vi_dash_main);
}
//use spacetimedb_sdk::spacetime_module::QueryBuilder;
/*
pub fn use_spacetime<T: Fn(QueryBuilder)>(query: T) {
let mut mize = use_context::<Mize>();
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
con.subscription_builder()
.on_applied(move |ctx| {
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
current.set(data.current.name.clone());
})
.add_query(query)
.subscribe();
con.db
.task_part()
.on_update(move |_, old: &TaskPart, new: &TaskPart| {
current.set(new.current.name.clone());
});
}
*/
//pub fn use_by_pk(identity: Identity)
//pub fn use_one<T>
pub fn c2vi_dash_main() -> Element {
let mut current = use_signal_sync(|| "".to_string());
let mut next = use_signal_sync(|| "".to_string());
//TaskPart::
//use_one()
use_effect(move || {
let mut mize = use_context::<Mize>();
let spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
Ok(val) => val,
Err(e) => {
e.log();
return;
},
};
let con = spacetime.con();
con.subscription_builder()
.on_applied(move |ctx| {
let data = match ctx.db.task_part().identity().find(&ctx.identity()) {
Some(val) => val,
None => {
debug!("can't find task part for user");
return;
},
};
current.set(data.current.name.clone());
if let Some(first_upcoming) = data.upcoming.first() {
next.set(first_upcoming.name.clone());
} else {
next.set("".to_string());
}
})
.add_query(|q| q.from.task_part().r#where(|t| t.identity.eq(con.identity())))
.subscribe();
con.db
.task_part()
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
current.set(new.current.name.clone());
if let Some(first_upcoming) = new.upcoming.first() {
next.set(first_upcoming.name.clone());
} else {
next.set("".to_string());
}
});
});
rsx! {
div {
style: "text-align: center; padding: 2rem; font-size: 120px;",
p {
"{current}"
}
p {
style: "font-size: 50px; color: grey; opacity: 0.7;",
"{next}"
}
}
}
}
pub fn c2vi_client(mize: &mut Mize) -> MizeResult<()> {
//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))?;
setup_tables(&conn)?;
let c2vi_part = C2vi {
mize: mize.clone(),
db: Mutex::new(Some(conn)),
};
mize.register_part(Box::new(c2vi_part))?;
// Register CLI subcommands
let mut cli = mize.get_part_native::<CliPart>("cli")?;
register_subcommands(&mut cli);
Ok(())
}
fn setup_tables(conn: &Connection) -> MizeResult<()> {
conn.execute(
"CREATE TABLE IF NOT EXISTS habitica_dailies (
id TEXT PRIMARY KEY,
text TEXT NOT NULL,
notes TEXT,
priority TEXT,
frequency TEXT
)",
[],
)
.map_err(|e| mize_err!("DB error creating habitica_dailies: {}", e))?;
conn.execute(
"CREATE TABLE IF NOT EXISTS habitica_log (
date TEXT PRIMARY KEY,
data TEXT NOT NULL
)",
[],
)
.map_err(|e| mize_err!("DB error creating habitica_log: {}", e))?;
Ok(())
}
fn register_subcommands(cli: &mut CliPart) {
cli.subcommand(
Command::new("c2vi-dash").about("Run the c2vi dashboard gui"),
|_matches, mut _mize| Ok(dioxus::launch(super::dashboard::main)),
);
cli.subcommand(
Command::new("bed").about("Command to run when c2vi goes to bed"),
|_matches, mut mize| {
save_habitica_dailies(&mut mize)?;
save_habitica_logs(&mut mize)?;
Ok(())
},
);
cli.subcommand(
Command::new("clearTodos")
.about("Clear all Habitica todos")
.alias("clt"),
|_matches, mut mize| {
let todos = {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
hab.get_tasks("todos")?
};
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
if let Some(arr) = todos.as_array() {
for todo in arr {
if let Some(id) = todo["id"].as_str() {
hab.delete_task(id)?;
}
}
}
Ok(())
},
);
cli.subcommand(
Command::new("listTodos")
.about("List all Habitica todos")
.alias("dut"),
|_matches, mut mize| {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
let todos = hab.get_tasks("todos")?;
if let Some(arr) = todos.as_array() {
for todo in arr {
if let Some(text) = todo["text"].as_str() {
println!("{}", text);
}
}
}
Ok(())
},
);
cli.subcommand(
Command::new("buyHealthPotion")
.about("Buy a health potion")
.alias("buyh"),
|_matches, mut mize| {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
hab.api_request(
reqwest::Method::POST,
"user/buy-health-potion".to_string(),
json!({}),
)?;
Ok(())
},
);
cli.subcommand(
Command::new("addActionItems")
.about("Add action items from temp file")
.alias("aal"),
|_matches, mut mize| add_action_items(&mut mize),
);
cli.subcommand(
Command::new("skip")
.about("Skip a Habitica todo")
.alias("sk")
.arg(Arg::new("num").default_value("1")),
|matches, mut mize| {
let num: usize = matches
.get_one::<String>("num")
.unwrap()
.parse()
.unwrap_or(1);
skip_habitica_todo(&mut mize, num)
},
);
cli.subcommand(
Command::new("dumpLog").about("Dump the complete Habitica log"),
|_matches, mut mize| dump_habitica_logs(&mut mize),
);
cli.subcommand(
Command::new("listDailies").about("List all dailies from local DB"),
|_matches, mut mize| {
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
c2vi.with_db(|conn| {
let mut stmt = conn
.prepare("SELECT id, text FROM habitica_dailies")
.map_err(|e| mize_err!("DB error: {}", e))?;
let rows = stmt
.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})
.map_err(|e| mize_err!("DB error: {}", e))?;
for row in rows {
let (id, text) = row.map_err(|e| mize_err!("DB error: {}", e))?;
println!("{}: {}", id, text);
}
Ok(())
})
},
);
cli.subcommand(
Command::new("printTask")
.about("Print a specific task from Habitica")
.arg(Arg::new("id").required(true)),
|matches, mut mize| {
let id = matches.get_one::<String>("id").unwrap();
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
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()
);
} else {
println!(
"{}",
serde_json::to_string_pretty(&data).unwrap_or_default()
);
}
Ok(())
},
);
}
// ============= Business Logic =============
fn format_date(dt: chrono::DateTime<Utc>) -> String {
dt.format("%Y-%m-%d").to_string()
}
/// Get the target date, adjusted for "save for yesterday" logic (before noon = yesterday)
fn get_target_date() -> (String, bool) {
let now = Utc::now();
let save_for_yesterday = now.hour() < 12;
let date = if save_for_yesterday {
now - chrono::Duration::days(1)
} else {
now
};
if save_for_yesterday {
println!(
"Saving logs for yesterday because it is {} hours of the day",
now.hour()
);
}
(format_date(date), save_for_yesterday)
}
/// Load existing log entry from DB, or return a default empty one
fn get_or_create_log(conn: &Connection, date: &str) -> MizeResult<Value> {
let mut stmt = conn
.prepare("SELECT data FROM habitica_log WHERE date = ?1")
.map_err(|e| mize_err!("DB error: {}", e))?;
let result: Option<String> = 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))
}
None => Ok(json!({
"dailies_done": [],
"dailies_skipped": [],
"todos_done": [],
"todos_skipped": []
})),
}
}
/// Upsert a log entry into the database
fn upsert_log(conn: &Connection, date: &str, data: &Value) -> MizeResult<()> {
let data_str =
serde_json::to_string(data).map_err(|e| mize_err!("JSON serialize error: {}", e))?;
conn.execute(
"INSERT INTO habitica_log (date, data) VALUES (?1, ?2)
ON CONFLICT(date) DO UPDATE SET data=excluded.data",
params![date, data_str],
)
.map_err(|e| mize_err!("DB error upserting log: {}", e))?;
Ok(())
}
fn save_habitica_dailies(mize: &mut Mize) -> MizeResult<()> {
println!("######### cli bed cmd #########");
// Fetch dailies from Habitica
let dailies = {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
hab.get_tasks("dailys")?
};
// Write to database
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
c2vi.with_db(|conn| {
if let Some(arr) = dailies.as_array() {
println!("Syncing {} dailies to database...", arr.len());
for daily in arr {
let id = daily["id"].as_str().unwrap_or("");
let text = daily["text"].as_str().unwrap_or("");
let notes = daily["notes"].as_str().unwrap_or("");
let priority = daily["priority"].to_string();
let frequency = daily["frequency"].as_str().unwrap_or("daily");
conn.execute(
"INSERT INTO habitica_dailies (id, text, notes, priority, frequency)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(id) DO UPDATE SET
text=excluded.text,
notes=excluded.notes,
priority=excluded.priority,
frequency=excluded.frequency",
params![id, text, notes, priority, frequency],
)
.map_err(|e| mize_err!("DB error writing daily: {}", e))?;
}
}
Ok(())
})
}
fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
let (date_str, save_for_yesterday) = get_target_date();
// Fetch dailies and completed todos from Habitica
let (dailies, completed_todos) = {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
let dailies = hab.get_tasks("dailys")?;
let completed_todos = hab.get_tasks("completedTodos")?;
(dailies, completed_todos)
};
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
c2vi.with_db(|conn| {
let mut data = get_or_create_log(conn, &date_str)?;
// Process dailies
if let Some(arr) = dailies.as_array() {
for daily in arr {
let is_due = if save_for_yesterday {
// Look at history for yesterday's status
daily["history"]
.as_array()
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
.map(|e| e["isDue"].as_bool().unwrap_or(false))
.unwrap_or(false)
} else {
daily["isDue"].as_bool().unwrap_or(false)
};
if !is_due {
continue;
}
let completed = if save_for_yesterday {
daily["history"]
.as_array()
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
.map(|e| e["completed"].as_bool().unwrap_or(false))
.unwrap_or(false)
} else {
daily["completed"].as_bool().unwrap_or(false)
};
let daily_id = daily["id"].as_str().unwrap_or("");
let daily_text = daily["text"].as_str().unwrap_or("");
if completed {
data["dailies_done"]
.as_array_mut()
.unwrap()
.push(json!(daily_id));
println!("daily-done: {}", daily_text);
} else {
data["dailies_skipped"]
.as_array_mut()
.unwrap()
.push(json!(daily_id));
println!("daily-skipped: {}", daily_text);
}
}
}
// Process completed todos
if let Some(arr) = completed_todos.as_array() {
let today_str = format_date(Utc::now());
for todo in arr {
let date_completed_str = todo["dateCompleted"].as_str().unwrap_or("");
// Parse the completion date (ISO 8601 format from Habitica)
let date_completed = chrono::DateTime::parse_from_rfc3339(date_completed_str)
.map(|d| format_date(d.with_timezone(&Utc)))
.unwrap_or_default();
// Check if already in the log for this date
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))
{
continue;
}
}
let todo_entry = json!({
"id": todo["id"],
"text": todo["text"],
"notes": todo.get("notes").unwrap_or(&json!("")),
"checklist": todo.get("checklist").unwrap_or(&json!([])),
"tags": todo.get("tags").unwrap_or(&json!([])),
});
// Save if completed on the target date
if date_completed == date_str {
println!("todo-done: {}", todo["text"].as_str().unwrap_or(""));
data["todos_done"]
.as_array_mut()
.unwrap()
.push(todo_entry.clone());
}
// 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);
}
}
}
upsert_log(conn, &date_str, &data)?;
Ok(())
})
}
fn skip_habitica_todo(mize: &mut Mize, num: usize) -> MizeResult<()> {
let (date_str, _) = get_target_date();
// Fetch todos from Habitica
let todos = {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
hab.get_tasks("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 {
let todo = match arr.get(i) {
Some(t) => t,
None => continue,
};
let todo_id = todo["id"].as_str().unwrap_or("");
// Delete the todo and score the skip habit
{
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
hab.delete_task(todo_id)?;
}
increment_habit(mize)?;
// Record in log
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
c2vi.with_db(|conn| {
let mut data = get_or_create_log(conn, &date_str)?;
data["todos_skipped"].as_array_mut().unwrap().push(json!({
"id": todo["id"],
"text": todo["text"],
"notes": todo.get("notes").unwrap_or(&json!("")),
"checklist": todo.get("checklist").unwrap_or(&json!([])),
"tags": todo.get("tags").unwrap_or(&json!([])),
}));
upsert_log(conn, &date_str, &data)?;
Ok(())
})?;
}
Ok(())
}
fn increment_habit(mize: &mut Mize) -> MizeResult<()> {
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
let habits = hab.get_tasks("habits")?;
if let Some(arr) = habits.as_array() {
for habit in arr {
if habit["text"].as_str() == Some("skipped a planned task") {
let id = habit["id"].as_str().unwrap_or("");
hab.api_request(
reqwest::Method::POST,
format!("tasks/{}/score/down", id),
json!({}),
)?;
return Ok(());
}
}
}
Ok(())
}
fn add_action_items(mize: &mut Mize) -> MizeResult<()> {
let initial_content = "\
gu 10min TIMER
uw 15min FROG
b coffee/tee/kakau
uw FROG
b breakfast, brush teeth, lüften
uw FROG
lunch
uw 15min TIMER of next days FROG
fw
dt 30min TIMER
fun 20:00 (smth that can be stopped easily)
se 21:40
read
bed 22:10";
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))?;
// Open in nvim for editing
std::process::Command::new("nvim")
.arg(&tmp_file)
.status()
.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 _ = std::fs::remove_file(&tmp_file);
let tasks: Vec<&str> = list_str
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.collect();
// Add tasks to Habitica
let mut task_ids = Vec::new();
{
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
for todo_text in &tasks {
let data = hab.api_request(
reqwest::Method::POST,
"tasks/user".to_string(),
json!({ "text": todo_text, "type": "todo" }),
)?;
let id = data["id"].as_str().unwrap_or("").to_string();
println!("Added to-do: '{}' with id '{}'", todo_text, id);
task_ids.push(id);
}
// Move all tasks to bottom
for task_id in &task_ids {
hab.api_request(
reqwest::Method::POST,
format!("tasks/{}/move/to/-1", task_id),
json!({}),
)?;
}
}
Ok(())
}
fn dump_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
c2vi.with_db(|conn| {
let mut stmt = conn
.prepare("SELECT date, data FROM habitica_log ORDER BY date")
.map_err(|e| mize_err!("DB error: {}", e))?;
let rows = stmt
.query_map([], |row| {
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()
);
}
Ok(())
})
}
pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResult<()> {
unsafe {
/*
std::env::set_var(
"PATH",
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
);
*/
std::env::set_var("SRC", SRC);
std::env::set_var("HOSTNAME", hostname);
}
let mut do_mize_restart = false;
let bin = match matches.get_one::<String>("bin") {
Some(val) => val.to_owned(),
None => {
"mize".to_owned()
}
};
let remote_src_dir = match hostname {
"lush" => "/home/me/here/config",
"mac" => "/data/external/config",
"files" => "/home/files/here/config",
"nico" => "/root/work/config-c2vi",
"ppc-hosting" => "/root/host/c2vi-config",
_ => "/home/me/here/config",
};
let mize_store_dir = match hostname {
"lush" => "/home/me/host/mize",
"mac" => "/home/me/host/mize",
"files" => "/home/files/storage/files/mize",
"nico" => "/root/work/app-data/mize",
"ppc-hosting" => "/root/host/mize",
_ => "/home/me/host/mize",
};
let rclone_flag = match hostname {
"nico" => "--chown=root:root",
"ppc-hosting" => "--chown=root:root",
_ => "",
};
sh!(echo cmd: rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?;
sh!(rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?;
let ssh_cmd = format!(
r#"
if ! tmux has-session -t mize-deploy 2>/dev/null; then
echo "Created new tmux session...."
tmux new-session -d -s mize-deploy "nix develop git+file://{remote_src_dir}/mize#buildShell --max-jobs 1"
else
echo "using existing session..."
fi
tmux send-keys -t mize-deploy "DO_MIZE_RESTART={do_mize_restart}" Enter
tmux send-keys -t mize-deploy "cargo build --manifest-path {remote_src_dir}/mize/Cargo.toml --bin {bin}" Enter
tmux send-keys -t mize-deploy 'BUILD_SUCCESS=$?' Enter
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > {mize_store_dir}/daemon.env' Enter
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "MIZE_CONFIG_FILES={mize_store_dir}/config.toml" >> {mize_store_dir}/daemon.env' Enter
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && [[ "$HOSTNAME" == "nico" ]] && echo "PATH=/root/work/path-extra:$PATH" >> {mize_store_dir}/daemon.env' Enter
tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && [[ "$DO_MIZE_RESTART" == "true" ]] && systemctl --user restart mize' Enter
"#
);
sh!(ssh $hostname $ssh_cmd)?;
Ok(())
}
pub fn deploy_spacetime(_: &ArgMatches, _: Mize) -> MizeResult<()> {
unsafe {
/*
std::env::set_var(
"PATH",
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
);
*/
std::env::set_var("SRC", SRC);
}
let cmd = format!(
r#"
if ! tmux has-session -t mize-deploy 2>/dev/null; then
echo "Created new tmux session...."
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize --impure"
else
echo "using existing session..."
tmux send-keys -t mize-deploy "echo hoooooooooooooo" Enter
fi
tmux send-keys -t mize-deploy "spacetime publish -p $SRC/mize/marts/spacetime/ --yes mize" Enter
"#
);
sh!(/bin/sh -c "$cmd")?;
Ok(())
}
pub fn obsidian_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
unsafe {
std::env::set_var(
"PATH",
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
);
std::env::set_var("SRC", SRC);
}
let cmd = format!(
r#"
if ! tmux has-session -t mize-deploy 2>/dev/null; then
echo "Created new tmux session...."
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize --impure"
else
echo "using existing session..."
fi
tmux send-keys -t mize-deploy "deno run --config {SRC}/mize/apps/obsidian-plugin/deno.json build" Enter
tmux send-keys -t mize-deploy "cp -r {SRC}/mize/apps/obsidian-plugin/dist/* ~/work/things/.obsidian/plugins/mize/" Enter
"#
);
sh!(/bin/sh -c "$cmd")?;
Ok(())
}
pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> {
unsafe {
std::env::set_var(
"PATH",
"/home/me/work/path-extra/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/home/me/.local/state/nix/profile/bin:/home/me/.nix-profile/bin",
);
std::env::set_var("SRC", SRC);
}
let cmd =
r#"
if ! tmux has-session -t mize-deploy 2>/dev/null; then
systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize#buildShell"
else
echo "using existing session..."
fi
tmux send-keys -t mize-deploy "cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize" Enter
tmux send-keys -t mize-deploy 'echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > /home/me/work/app-data/mize/daemon.env' Enter
tmux send-keys -t mize-deploy "systemctl --user restart mize" Enter
"#;
sh!(/bin/sh -c "$cmd")?;
Ok(())
}
pub fn nico_notes_sync_thread(mize: Mize) -> MizeResult<()> {
tracing::info!("starting nico_notes_sync_thread");
loop {
let res = sh!(/bin/bash -c r#"
cd /root/work/nico/notes/c2vi
git add .
git commit -m "nico notes sync"
git pull --rebase
git push
cd /root/work/nico/notes/nexus
git add .
git commit -m "nico nexus notes sync"
git pull --rebase
git push
cd /root/work/nico/notes/ppc-wiki
git add .
git commit -m "nico ppc-wiki notes sync"
git pull --rebase
git push
"#);
if let Err(e) = res {
tracing::error!("error in nico_notes_sync_thread: {}", e);
};
std::thread::sleep(std::time::Duration::from_mins(2));
}
}

View File

@@ -1,357 +0,0 @@
use clap::{ArgMatches, Command};
use dioxus::core::SpawnIfAsync;
use std::{cell::RefCell, collections::HashMap};
use mize::{
item::ItemData, mize_err, mize_part, proto::MizeMessage, Mize, MizeError, MizePart,
MizePartCreate, MizeResult,
};
thread_local! {
pub static MIZE_CLI_SPAWN: RefCell<Option<CliSpawn>> = const { RefCell::new(None) };
}
struct CliSpawn {
wait_rx: flume::Receiver<()>,
conn_id: Option<u64>,
mize: Mize,
}
pub fn cli_spawn_setup(mize: Mize, conn_id: u64, wait_rx: flume::Receiver<()>) -> MizeResult<()> {
MIZE_CLI_SPAWN.with(|cell| {
*cell.borrow_mut() = Some(CliSpawn {
wait_rx,
conn_id: Some(conn_id),
mize,
});
Ok(())
})
}
pub fn cli_spawn(mize: &mut Mize, cmd: String) -> MizeResult<()> {
// spawn locally
if !mize.get_config("cli.daemonMode").unwrap_or(ItemData::new()).as_bool().unwrap_or(false) {
std::process::Command::new("/bin/sh")
.arg("-c")
.arg(&cmd)
.status()?;
return Ok(());
}
// for spawning in the client if we are running in the daemon (which is eg attached to the users tty)
MIZE_CLI_SPAWN.with(|cell| {
if let Some(spawn) = cell.borrow().as_ref() {
let conn_id = spawn.conn_id.ok_or(mize_err!("no conn_id set"))?;
let conn = spawn.mize.get_connection(conn_id)?;
conn.send(MizeMessage::new_custom("cli.spawn", cmd, conn_id))?;
log::info!("before wait_rx.recv()");
spawn.wait_rx.recv()?;
log::info!("after wait_rx.recv()");
Ok(())
} else {
Err(mize_err!("no cli spawn thread_local available"))
}
})
}
#[mize_part("cli")]
#[derive(Default)]
pub struct CliPart {
mize: Mize,
cmds: HashMap<String, Command>,
current_cmd_name: String,
actions:
HashMap<String, Box<dyn Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>>,
parsers: Option<Vec<Box<dyn Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
}
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
let command = Command::new("marts-cli").allow_external_subcommands(true);
let (spawn_wait_tx, spawn_wait_rx) = flume::unbounded::<()>();
let mut cli_part = CliPart {
mize: mize.clone(),
cmds: HashMap::new(),
current_cmd_name: "marts-cli".to_string(),
actions: HashMap::new(),
parsers: Some(Vec::new()),
};
cli_part.cmds.insert("marts-cli".to_string(), command);
mize.add_listener(
"cli",
Box::new(move |msg, mize| cli_msg_handler(msg, mize, spawn_wait_rx.clone())),
)?;
mize.add_listener(
"cli.spawn_done",
Box::new(move |_msg, _mize| {
log::info!("before send");
spawn_wait_tx.send(());
log::info!("after send");
Ok(())
}),
)?;
mize.add_part(Box::new(cli_part))
}
impl MizePartCreate for CliPart {}
impl MizePart for CliPart {
fn init(&mut self, _mize: &mut Mize) -> MizeResult<()> {
Ok(())
}
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
if mize
.get_config("cli.daemonMode")
.unwrap_or(ItemData::new())
.as_bool()
.unwrap_or(false)
{
return Ok(());
}
let matches = self
.cmds
.remove(&self.current_cmd_name)
.unwrap()
.get_matches();
let sub_cmd = match matches.subcommand_name() {
Some(sub_cmd) => sub_cmd,
None => {
log::info!("No subcommand provided");
return Ok(());
}
};
let action = match self.actions.remove(sub_cmd) {
Some(action) => action,
None => {
// external parsers
for parser in self.parsers.take().unwrap() {
parser(
mize.clone(),
matches
.subcommand_matches(sub_cmd)
.unwrap()
.get_many::<String>("")
.unwrap()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
)?;
}
return Ok(());
}
};
action(matches.subcommand_matches(sub_cmd).unwrap(), mize.clone())?;
Ok(())
}
fn opts(&self, mize: &mut Mize) {
mize.new_opt("cli.name");
}
}
fn cli_msg_handler(
mut msg: MizeMessage,
mize: &mut Mize,
spawn_wait_rx: flume::Receiver<()>,
) -> MizeResult<()> {
let tmp = msg.data()?;
let data = tmp
.cbor()
.as_array()
.ok_or(mize_err!("cli message data was not an array"))?;
let full_cmd = data
.iter()
.nth(0)
.cloned()
.ok_or(mize_err!("cli message data was empty"))?;
let full_cmd_text = full_cmd
.as_text()
.ok_or(mize_err!("cli cmd was not text"))?;
let cmd = full_cmd_text
.split("/")
.last()
.ok_or(mize_err!("failed to get cmd"))?;
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
let command = cli
.cmds
.get(cmd)
.ok_or(mize_err!("no command {cmd} in cli part"))?;
let matches = command
.clone()
.get_matches_from(data.iter().map(|v| v.as_text().unwrap()));
let sub_cmd_name = match matches.subcommand_name() {
Some(sub_cmd) => sub_cmd,
None => {
cli.send_stderr(&msg, "No subcommand provided".to_string())?;
return Ok(());
}
};
let action = match cli.actions.get_mut(sub_cmd_name) {
Some(action) => action,
None => {
return Ok(());
}
};
let original_msg = msg.clone();
let mize_clone = mize.clone();
mize::MIZE_STDOUT.with(move |stdout| {
*stdout.borrow_mut() = Some(Box::new(move |string| {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stdout", string, conn_id);
let conn = mize_clone.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}))
});
cli_spawn_setup(mize.clone(), msg.conn_id, spawn_wait_rx)?;
action(
matches.subcommand_matches(sub_cmd_name).unwrap(),
mize.clone(),
)?;
cli.send_done(&msg)?;
Ok(())
}
impl CliPart {
pub fn set_command(&mut self, cmd: &str) {
self.current_cmd_name = cmd.to_owned();
if !self.cmds.contains_key(cmd) {
self.cmds
.insert(cmd.to_owned(), Command::new(cmd.to_owned()));
}
}
pub fn send_stderr(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stderr", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_stdout(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stdout", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_spawn(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.spawn", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_done(&mut self, original_msg: &MizeMessage) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.done", ItemData::new(), conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn subcommand<T: Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>(
&mut self,
subcmd: Command,
action: T,
) -> MizeResult<()> {
let name = subcmd.get_name().to_string();
let cmd = self
.cmds
.remove(&self.current_cmd_name)
.unwrap()
.subcommand(subcmd);
self.cmds.insert(self.current_cmd_name.clone(), cmd);
self.actions.insert(name, Box::new(action));
Ok(())
}
pub fn add_sub_parser<T: Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>(
&mut self,
parser: T,
) -> MizeResult<()> {
self.parsers.as_mut().unwrap().push(Box::new(parser));
Ok(())
}
pub fn with_cmd(
&mut self,
func: fn(mize: Mize, cmd: Command) -> MizeResult<Command>,
) -> MizeResult<()> {
let cmd = self.cmds.remove(&self.current_cmd_name).ok_or(mize_err!(
"current command {} not found",
self.current_cmd_name
))?;
let cmd = func(self.mize.clone(), cmd)?;
self.cmds.insert(self.current_cmd_name.clone(), cmd);
Ok(())
}
pub fn run(&mut self, cmdline: Vec<String>) -> MizeResult<()> {
let cmd_name = cmdline.iter().nth(0).ok_or(mize_err!("empty cmdline"))?;
let cmd = self.cmds.remove(cmd_name).ok_or(mize_err!("cmd {} not found in CliPart", cmd_name))?;
let matches = cmd.get_matches_from(cmdline);
let sub_name = matches.subcommand_name().ok_or(mize_err!("subcommand not found"))?;
let sub_matches = matches.subcommand_matches(sub_name).ok_or(mize_err!("subcommand matches not found"))?;
let action = self.actions.remove(sub_name).ok_or(mize_err!("action for subcommand not found"))?;
action(sub_matches, self.mize.clone())?;
Ok(())
}
}
// impl smth like this to run cli commands in the daemon.
/*
pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> {
use std::io::Write;
use std::os::unix::net::UnixStream;
let store_path =
std::env::var("MIZE_STORE").map_err(|_| mize_err!("MIZE_STORE not set in environment"))?;
let sock_path = store_path + "/sock";
println!("sock_path: {}", sock_path);
let mut cmd_line = std::env::args().collect::<Vec<_>>();
if let Some(bin_name) = bin_name {
cmd_line[0] = bin_name.to_owned();
}
let msg = MizeMessage::new_custom("cli", cmd_line, 0);
let mut stream = UnixStream::connect(sock_path)?;
stream.write_all(&msg.bytes())?;
// Optional: Ensure all buffered data is pushed to the socket
stream.flush()?;
let mut reader = BufReader::new(stream.try_clone()?);
loop {
let msg: Value = ciborium::from_reader(&mut reader)?;
let mut msg = MizeMessage::new(msg, 0);
match msg.cmd()? {
MessageCmd::Custom(kind) => {
if kind == "cli.done".into_item_data() {
break;
}
if kind == "cli.stdout".into_item_data() {
println!("{}", msg.data()?.value_string()?);
}
if kind == "cli.stderr".into_item_data() {
eprintln!("{}", msg.data()?.value_string()?);
}
if kind == "cli.spawn".into_item_data() {
let cmd = msg.data()?.value_string()?;
std::process::Command::new("/bin/sh")
.arg("-c")
.arg(&cmd)
.status()?;
let done_msg = MizeMessage::new_custom("cli.spawn_done", ItemData::new(), 0);
stream.write_all(&done_msg.bytes())?;
}
}
_ => {
continue;
}
}
}
Ok(())
}
*/

View File

@@ -1,17 +0,0 @@
use mize::{Mize, MizeResult, MizeError, mize_err};
use crate::CliPart;
use clap::Arg;
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
pub fn part_dev(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<CliPart>("cli")?;
cli.set_command("dev");
mize.add_name_only_part("dev");
Ok(())
}

View File

@@ -1,167 +0,0 @@
pub use mize::*;
// ========== WASM-specific imports (Obsidian plugin) ==========
#[cfg(feature = "target-obsidian")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "target-obsidian")]
use js_sys;
#[cfg(feature = "target-obsidian")]
use web_sys;
#[cfg(feature = "target-obsidian")]
use crate::obsidian::{ObsidianPart, MartsCommand};
// ========== Native-specific imports (OS daemon) ==========
#[cfg(feature = "target-os")]
use serenity::{
async_trait as serenity_async_trait,
client::{Client, Context, EventHandler},
model::{
channel::Message,
gateway::Ready,
id::ChannelId,
},
prelude::GatewayIntents,
};
#[cfg(feature = "target-os")]
use mize::async_trait;
#[derive(Clone)]
#[mize_part]
pub struct DiscordPart {
pub mize: Mize,
// Native: serenity client
#[cfg(feature = "target-os")]
pub client: Option<Client>,
}
impl DiscordPart {
// ========== WASM (Obsidian plugin) methods ==========
/// Read a file from the Obsidian vault using the Obsidian API
#[cfg(feature = "target-obsidian")]
pub fn read_obsidian_file(&self, path: &str) -> MizeResult<String> {
// TODO: Implement using js_sys to call Obsidian's vault.read() or similar
// Example approach:
use wasm_bindgen::JsCast;
use js_sys::Reflect;
let obsidian_part = self.mize.get_part_native::<ObsidianPart>("obsidian")?;
let plugin = &obsidian_part.plugin();
let app = Reflect::get(plugin, &"app".into())?;
let vault = Reflect::get(&app, &"vault".into())?;
let content = Reflect::call(&vault, &"read".into(), &path.into())?;
content.as_string().ok_or_else(|| mize_err!("Failed to read file"))
}
/// Send content to Discord channel using Discord REST API
#[cfg(feature = "target-obsidian")]
pub fn send_to_discord_rest(&self, content: String, channel_id: u64) -> MizeResult<()> {
// TODO: Implement using web_sys to call Discord REST API
// 1. Get token: mize.get_config("discord.token")
// 2. POST to https://discord.com/api/v10/channels/{channel_id}/messages
// 3. Headers: Authorization: Bot <token>, Content-Type: application/json
// 4. Body: {"content": content}
Ok(())
}
// ========== Native (OS daemon) methods ==========
/// Start the serenity Discord bot
#[cfg(feature = "target-os")]
pub async fn start_serenity_bot(&mut self, mize: &mut Mize) -> MizeResult<()> {
let token = mize.get_config("discord.token")?.value_string()?;
// Define event handler
struct Handler;
#[serenity_async_trait]
impl EventHandler for Handler {
async fn ready(&self, _: Context, ready: Ready) {
println!("Discord bot logged in as {}", ready.user.name);
}
// TODO: Add message handler for receiving commands from plugin
// This is where you'll implement communication with the Obsidian plugin
// Example:
// async fn message(&self, ctx: Context, msg: Message) {
// if msg.content.starts_with("!send") {
// // Parse command and send file to channel
// }
// }
}
let mut client = Client::builder(&token, GatewayIntents::non_privileged())
.event_handler(Handler)
.await?;
// Spawn the client in a tokio task
tokio::spawn({
let client_clone = client.clone();
async move {
if let Err(why) = client_clone.start().await {
eprintln!("Discord client error: {:?}", why);
}
}
});
self.client = Some(client);
Ok(())
}
}
impl MizePart for DiscordPart {
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
// WASM: Add command to Obsidian
#[cfg(feature = "target-obsidian")]
{
let obsidian_part = mize.get_part_native::<ObsidianPart>("obsidian")?;
let callback = {
let mize_clone = mize.clone();
move |mize_inner: Mize| -> MizeResult<()> {
// Get configuration values
let file_path = mize_inner.get_config("sc2.shared_daily_journal_file")?
.value_string()?;
let channel_id_str = mize_inner.get_config("sc2.shared_daily_journal_channel")?
.value_string()?;
let channel_id = channel_id_str.parse::<u64>()?;
// Get DiscordPart and send the file
let mut mize_mut = mize_inner;
let discord_part = mize_mut.get_part_native::<DiscordPart>("discord")?;
let content = discord_part.read_obsidian_file(&file_path)?;
// TODO: COMMUNICATION WITH DAEMON GOES HERE
// For now, we send directly using REST API.
// Replace this with your plugin-to-daemon messaging logic.
discord_part.send_to_discord_rest(content, channel_id)?;
Ok(())
}
};
obsidian_part.add_command(MartsCommand {
id: "send_to_discord".to_string(),
name: "Send to Discord".to_string(),
callback: Box::new(callback),
})?;
}
Ok(())
}
// Native: Override async_init to start serenity bot
#[cfg(feature = "target-os")]
async fn async_init(&mut self, mize: &mut Mize) -> MizeResult<()> {
self.start_serenity_bot(mize).await
}
}
impl MizePartCreate for DiscordPart {}
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(DiscordPart {
mize: mize.clone(),
#[cfg(feature = "target-os")]
client: None,
}))
}

View File

@@ -1,128 +0,0 @@
use chrono::{DateTime, Utc};
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
use reqwest::{blocking::Client, blocking::Response, header, Method};
use serde_json::{json, Value};
use std::thread::sleep;
use std::time::Duration;
#[mize_part]
#[derive(Default)]
pub struct Habitica {
mize: Mize,
client: Client,
}
pub fn habitica(mize: &mut Mize) -> MizeResult<()> {
let client = Client::new();
mize.register_part(Box::new(Habitica {
mize: mize.clone(),
client,
}))
}
impl MizePartCreate for Habitica {}
impl MizePart for Habitica {
fn opts(&self, mize: &mut Mize) {
mize.new_opt("habitica.api_url");
mize.new_opt("habitica.user_id");
mize.new_opt("habitica.api_token");
mize.new_opt("habitica.client_name");
}
}
impl Habitica {
pub fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult<Value> {
let api_url = self.mize.get_config("habitica.api_url")?.to_string();
let user_id = self.mize.get_config("habitica.user_id")?.to_string();
let api_token = self.mize.get_config("habitica.api_token")?.to_string();
let client_name = self.mize.get_config("habitica.client_name")?.to_string();
let mut headers = header::HeaderMap::new();
headers.insert(
"Content-Type",
header::HeaderValue::from_static("application/json"),
);
headers.insert("x-api-user", header::HeaderValue::from_str(&user_id)?);
headers.insert("x-api-key", header::HeaderValue::from_str(&api_token)?);
headers.insert("x-client", header::HeaderValue::from_str(&client_name)?);
let url = format!("{}/{}", api_url, path.trim_start_matches('/'));
let mut request_builder = self.client.request(method, &url).headers(headers);
if data != json!({}) {
request_builder = request_builder.json(&data);
}
let response = request_builder.send()?;
handle_rate_limit(&response);
if !response.status().is_success() {
let status = response.status();
let text = response.text().unwrap_or_default();
log::info!("request_url: {}", url);
return Err(mize_err!(
"Habitica API error: {} {} - {}",
status.as_u16(),
status.canonical_reason().unwrap_or(""),
text
));
}
let json_response: Value = response.json()?;
Ok(json_response.get("data").cloned().unwrap_or(Value::Null))
}
pub fn get_tasks(&mut self, task_type: &str) -> MizeResult<Value> {
self.api_request(
Method::GET,
format!("tasks/user?type={}", task_type),
json!({}),
)
}
pub fn delete_task(&mut self, id: &str) -> MizeResult<Value> {
self.api_request(Method::DELETE, format!("tasks/{}", id), json!({}))
}
}
fn handle_rate_limit(response: &Response) {
let headers = response.headers();
let limit = headers
.get("X-RateLimit-Limit")
.and_then(|v| v.to_str().ok())
.unwrap_or("NONE");
let remaining = headers
.get("X-RateLimit-Remaining")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(10);
let reset = headers
.get("X-RateLimit-Reset")
.and_then(|v| v.to_str().ok());
log::info!(
"RateLimit: {} | Remaining: {} | Reset: {}",
limit,
remaining,
reset.unwrap_or("N/A")
);
if remaining < 2 {
if let Some(reset_str) = reset {
if let Ok(reset_date) = DateTime::parse_from_rfc2822(reset_str) {
let now = Utc::now();
let wait_duration = reset_date.signed_duration_since(now).to_std();
if let Ok(wait_duration) = wait_duration {
let wait_ms = wait_duration.as_millis() as u64 + 1000;
log::info!(
"Waiting {} secs for next rate limit window...",
(wait_ms / 1000)
);
sleep(Duration::from_millis(wait_ms));
}
}
}
}
}

View File

@@ -1,4 +0,0 @@
export * from "./task/obsidian-otask-parse";
export * from "./c2vi/obsidian-canvas-patch";
export * from "./js/stub_deno";

View File

@@ -1,53 +0,0 @@
use deno_core::extension;
use deno_core::op2;
use deno_core::OpState;
use mize::{Mize, MizeError};
use std::sync::Arc;
use crate::CliPart;
use crate::JsPart;
#[op2(fast)]
fn op_mize_get_part(#[string] name: &str) {
log::info!("js wants the part {}", name);
}
#[op2]
#[string]
fn op_mize_get_config(state: &mut OpState, #[string] key: &str) -> String {
let mize: Mize = state.borrow::<Mize>().clone();
format!("config_value_for_{}", key)
}
#[op2(fast)]
fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
let mize: Mize = state.borrow::<Mize>().clone();
log::info!("adding part {}", key);
}
#[op2(fast)]
fn op_cli_subcommand(state: &mut OpState, #[string] name: String) {
let mut mize: Mize = state.borrow::<Mize>().clone();
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
cli.subcommand(
clap::Command::new(name.clone()),
move |_sub_matches, mut mize| {
let mut js = mize.get_part_native::<JsPart>("js").unwrap();
js.eval("log(\"cli command being called\")".to_string())?;
js.eval(format!("mize.vars.cli.subcommand({name})"))?;
Ok(())
},
);
}
// Create an extension with your custom ops
extension!(
my_extension,
ops = [op_mize_get_part, op_mize_get_config],
options = {
mize: Mize,
},
state = |state: &mut OpState, config: Config| {
state.put::<Mize>(config.mize);
},
);

View File

@@ -1,11 +0,0 @@
import { Mize } from "./stub_deno.ts";
function argsToMessage(...args) {
return args.map((arg) => JSON.stringify(arg)).join(" ");
}
globalThis.log = (...args) => {
Deno.core.print(`[out]: ${argsToMessage(...args)}\n`, false);
};
globalThis.mize = new Mize();

View File

@@ -1,136 +0,0 @@
use deno_core::error::AnyError;
use deno_core::url::Url;
use deno_core::v8::OneByteConst;
use deno_core::{
ascii_str_include, extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions,
RuntimeOptions,
};
use deno_core::{FastStaticString, FsModuleLoader};
use mize::async_trait;
use mize::instance::MizePartCreate;
use mize::{mize_part, Mize, MizePart, MizeResult};
use std::future::Future;
use std::path::Path;
use std::pin::Pin;
use std::rc::Rc;
use std::str::FromStr;
use tokio::runtime::Builder;
use tokio::task::{spawn_local, LocalSet};
mod glue_deno;
#[mize_part("js")]
#[derive(Default)]
pub struct JsPart {
mize: Mize,
sender: Option<flume::Sender<JsRuntimeThreadMessage>>,
}
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = MizeResult<()>> + 'a>>;
pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static>;
impl MizePart for JsPart {
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
log::info!("js part run");
self.sender
.as_ref()
.unwrap()
.send(JsRuntimeThreadMessage::DoRunPhase)
.unwrap();
Ok(())
}
}
pub fn js(mize: &mut Mize) -> MizeResult<()> {
let (sender, receiver) = flume::unbounded::<JsRuntimeThreadMessage>();
let mize_clone = mize.clone();
// the thread which will run any js
mize.spawn_and_wait("js_runtime_thread", || {
js_runtime_thread(mize_clone, receiver)
})?;
mize.add_part(Box::new(JsPart {
mize: mize.clone(),
sender: Some(sender),
}))
}
pub fn part_from_file(
mize: &mut Mize,
name: &'static str,
code: FastStaticString,
) -> MizeResult<()> {
let mut js = mize.get_part_native::<JsPart>("js")?;
js.execute_init_js(code)?;
Ok(())
}
impl JsPart {
pub fn execute_init_js(&mut self, code: FastStaticString) -> MizeResult<()> {
self.sender
.as_ref()
.unwrap()
.send(JsRuntimeThreadMessage::RunInitJs(code))
.unwrap();
Ok(())
}
pub fn eval(&mut self, code: String) -> MizeResult<()> {
self.sender
.as_ref()
.unwrap()
.send(JsRuntimeThreadMessage::RunJs(code))
.unwrap();
Ok(())
}
}
fn js_runtime_thread(
mize: Mize,
receiver: flume::Receiver<JsRuntimeThreadMessage>,
) -> MizeResult<()> {
let mut js_runtime = JsRuntime::new(RuntimeOptions {
//module_loader: Some(Rc::new(FsModuleLoader)),
//extensions: vec![glue_deno::my_extension::init(mize.clone())],
..Default::default()
});
js_runtime
.execute_script("[stub]", ascii_str_include!("../deno_dist/glue_deno.js"))
.unwrap();
loop {
log::info!("js thread waiting for smth");
let msg = receiver.recv().unwrap();
log::info!("js thread got smth");
match msg {
JsRuntimeThreadMessage::RunInitJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[init]", js_code) {
log::info!("err: {}", err);
}
log::info!("done running js");
}
JsRuntimeThreadMessage::RunJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[idk]", js_code) {
log::info!("err: {}", err);
}
log::info!("done running js");
}
JsRuntimeThreadMessage::DoRunPhase => {
if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") {
log::info!("err: {}", err);
}
log::info!("done with runPhase");
return Ok(());
}
}
}
}
enum JsRuntimeThreadMessage {
//Closure(Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>),
//AsyncClosure(BoxClosure),
RunInitJs(FastStaticString),
RunJs(String),
DoRunPhase,
}

View File

@@ -1,18 +0,0 @@
export class Mize {
async get_config(key: string): Promise<any> {
return "hiiiiiii";
}
async add_opts(opts: any): Promise<void> {}
async add_part(part: any): Promise<void> {}
async report_err(err: MizeError) {}
runPhase() {
Deno.core.print("runPhase in js \n");
}
}
type MizeError = string;
export class Cli {
constructor(mize: Mize) {}
async sub_command(name: string, cmd: any): Promise<void> {}
}

View File

@@ -1,54 +0,0 @@
#![feature(oneshot_channel)]
#[cfg(feature = "target-os")]
pub mod cli;
#[cfg(feature = "target-os")]
pub use cli::*;
#[cfg(feature = "target-os")]
pub mod habitica;
#[cfg(feature = "target-os")]
pub use habitica::*;
#[cfg(feature = "target-os")]
pub mod c2vi {
pub mod part;
pub mod dashboard;
}
pub use c2vi::part::*;
#[cfg(feature = "target-os")]
pub mod dev;
#[cfg(feature = "target-os")]
pub use dev::part_dev;
#[cfg(feature = "target-obsidian")]
pub mod obsidian;
#[cfg(feature = "target-obsidian")]
pub use obsidian::*;
pub mod spacetime;
pub use spacetime::*;
pub mod auth {
pub mod client;
pub use client::*;
}
pub use auth::*;
pub mod ppc;
#[cfg(feature = "target-obsidian")]
pub mod matrix;
#[cfg(feature = "target-obsidian")]
pub use matrix::part_matrix;
pub mod sci;
//#[cfg(feature = "target-os")]
//pub mod js;
//#[cfg(feature = "target-os")]
//pub use js::*;
pub use generated;

View File

@@ -1,247 +0,0 @@
use mize::{Mize, MizeResult, MizePart, MizePartCreate, mize_part};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use js_sys::Function;
use web_sys::HtmlElement;
use matrix_sdk::{
Client, ClientConfig, SyncState,
ruma::{
user_id::UserId,
events::room::message::RoomMessageEventContent,
},
};
#[cfg(feature = "target-js")]
mod ui;
use ui::mount_chat_app;
const ROOM_ID: &str = "!XwqdXGPlmaLatdGUiN:ppc.social";
const ACCOUNT_NAME: &str = "default";
const INITIAL_LOAD_LIMIT: usize = 50;
/// Matrix part that manages Matrix client and UI
#[mize_part("matrix")]
#[derive(Clone)]
pub struct MatrixPart {
mize: Mize,
pub client: Option<Client>,
pub timeline: Vec<matrix_sdk::SyncRoomEvent>,
pub message_draft: String,
pub edit_event_id: Option<String>,
}
impl MizePart for MatrixPart {}
impl MizePartCreate for MatrixPart {}
impl MatrixPart {
/// Create a new MatrixPart
pub fn new(mize: Mize) -> Self {
Self {
mize,
client: None,
timeline: Vec::new(),
message_draft: String::new(),
edit_event_id: None,
}
}
/// Initialize the Matrix client from config
pub async fn init_client(&mut self) -> Result<(), String> {
let homeserver = self.mize
.get_config(&format!("matrix.accounts.{}.homeserver", ACCOUNT_NAME))
.map_err(|e| format!("Failed to get homeserver config: {:?}", e))?;
let access_token = self.mize
.get_config(&format!("matrix.accounts.{}.access_token", ACCOUNT_NAME))
.map_err(|e| format!("Failed to get access_token config: {:?}", e))?;
let user_id_str = self.mize
.get_config(&format!("matrix.accounts.{}.user_id", ACCOUNT_NAME))
.map_err(|e| format!("Failed to get user_id config: {:?}", e))?;
let device_id = self.mize
.get_config(&format!("matrix.accounts.{}.device_id", ACCOUNT_NAME))
.map_err(|e| format!("Failed to get device_id config: {:?}", e))?;
let user_id = UserId::parse(&user_id_str)
.map_err(|e| format!("Invalid user_id: {:?}", e))?;
let config = ClientConfig::new()
.homeserver_url(homeserver)
.device_id(device_id);
let client = Client::new_with_config(user_id, Some(access_token), config)
.await
.map_err(|e| format!("Failed to create client: {:?}", e))?;
self.client = Some(client.clone());
// Start sync in background
self.start_sync(client).await?;
Ok(())
}
/// Start sync and set up event listeners
async fn start_sync(&mut self, client: Client) -> Result<(), String> {
let room_id = ROOM_ID.to_string();
let mize = self.mize.clone();
// Start sync
let sync_token = client.sync_token().await;
let mut sync_state = SyncState::new(sync_token);
let client_clone = client.clone();
spawn_local(async move {
loop {
match client_clone.sync(&mut sync_state).await {
Ok(response) => {
// Process sync response - update MatrixPart via mize
if let Some(rooms) = response.rooms {
if let Some(join) = rooms.join {
if let Some(room_events) = join.get(&room_id) {
// Get MatrixPart and update timeline
if let Ok(mut matrix_part) = mize.get_part_native::<MatrixPart>("matrix") {
for event in &room_events.timeline.events {
if event.room_id() == room_id {
matrix_part.timeline.push(event.clone());
// Keep only last INITIAL_LOAD_LIMIT events
if matrix_part.timeline.len() > INITIAL_LOAD_LIMIT {
matrix_part.timeline.remove(0);
}
}
}
}
}
}
}
// Check if we should stop syncing
if sync_state.should_stop() {
break;
}
}
Err(e) => {
console_log!("Sync error: {:?}", e);
break;
}
}
}
});
Ok(())
}
/// Send a message to the room
pub async fn send_message(&mut self, room_id: &str, content: Option<>) -> MizeResult<()> {
let client = self.client.as_ref()
.ok_or_else(|| mize::error::Error::new("Client not initialized"))?;
if self.message_draft.is_empty() {
return Ok(());
}
let content = RoomMessageEventContent::text_plain(&self.message_draft);
if let Some(_event_id) = &self.edit_event_id {
// Editing existing message - placeholder for now
console_log!("Editing message (not yet implemented)");
}
// Send message
client.send_room_message(room_id, content).await?;
// Clear draft after sending
self.message_draft.clear();
self.edit_event_id = None;
Ok(())
}
}
/// Entry point for the Matrix part
pub fn part_matrix(mize: &mut Mize) -> MizeResult<()> {
#[cfg(feature = "target-obsidian")] {
use crate::ObsidianPart;
// Get the Obsidian part
let obsidian = mize.get_part_native::<ObsidianPart>("obsidian")?;
// Create and register MatrixPart
let matrix_part = MatrixPart::new(mize.clone());
mize.register_part(Box::new(matrix_part));
// Get the registered MatrixPart to initialize it
let mize_clone = mize.clone();
spawn_local(async move {
if let Ok(mut matrix_part) = mize_clone.get_part_native::<MatrixPart>("matrix") {
if let Err(e) = matrix_part.init_client().await {
console_log!("Failed to initialize Matrix client: {}", e);
}
}
});
// Create a JS function that will be called when the view is opened
let mize_for_view = mize.clone();
let view_creator = Closure::<dyn Fn(JsValue)>::wrap(Box::new(move |leaf: JsValue| {
if let Some(element) = extract_dom_element(leaf) {
// Get MatrixPart state for the UI
if let Ok(matrix_part) = mize_for_view.get_part_native::<MatrixPart>("matrix") {
let timeline = matrix_part.timeline.clone();
let message_draft = matrix_part.message_draft.clone();
let edit_event_id = matrix_part.edit_event_id.clone();
mount_chat_app(element, timeline, message_draft, edit_event_id, mize_for_view.clone());
}
} else {
console_log!("Failed to extract DOM element from leaf");
}
}));
// Convert closure to JS Function
let view_creator_fn: &Function = view_creator.as_ref().unchecked_ref();
// Register the view with Obsidian
ObsidianPart::register_view(&obsidian.plugin(), "matrix-chat-view", view_creator_fn)?;
// Prevent the closure from being dropped
view_creator.forget();
}
Ok(())
}
/// Extract DOM element from Obsidian leaf JSValue
fn extract_dom_element(leaf: JsValue) -> Option<HtmlElement> {
use wasm_bindgen::JsCast;
use js_sys::Reflect;
// Try to get the containerEl from the leaf
let container_value = Reflect::get(&leaf, &"containerEl".into()).ok()?;
let container = container_value.dyn_into::<HtmlElement>().ok()?;
// Create a div to mount into
let doc = web_sys::window()?.document()?;
let mount_point = doc.create_element("div").ok()?;
mount_point.set_attribute("style", "height: 100%; width: 100%;").ok()?;
container.append_child(&mount_point).ok()?;
mount_point.dyn_into::<HtmlElement>().ok()
}
// Console log macros
#[macro_export]
macro_rules! console_log {
($($t:tt)*) => {
web_sys::console::log_1(&format!($($t)*).into());
};
}
#[macro_export]
macro_rules! console_error {
($($t:tt)*) => {
web_sys::console::error_1(&format!($($t)*).into());
};
}

View File

@@ -1,269 +0,0 @@
use dioxus::prelude::*;
use dioxus_web::events::FormEvent;
use mize::Mize;
use matrix_sdk::SyncRoomEvent;
const ROOM_ID: &str = "!XwqdXGPlmaLatdGUiN:ppc.social";
/// Props for the chat component
#[derive(Props, Clone)]
pub struct ChatProps {
pub mize: Mize,
pub initial_timeline: Vec<SyncRoomEvent>,
pub initial_draft: String,
pub initial_edit_id: Option<String>,
}
/// Message component - renders a single message
#[component]
pub fn Message(cx: Scope, event: SyncRoomEvent) -> Element {
let sender = event.sender().to_string();
let timestamp = event.origin_server_ts();
// Extract display name from MXID (basic version)
let display_name = if let Some(at_pos) = sender.find('@') {
if let Some(colon_pos) = sender.rfind(':') {
&sender[at_pos + 1..colon_pos]
} else {
&sender[at_pos + 1..]
}
} else {
&sender
};
// Format timestamp
let time_str = {
let dt = js_sys::Date::new(&(timestamp as f64));
dt.to_locale_string()
};
// Extract message content
let content = event
.as_original()
.and_then(|e| e.content().deserialize().ok())
.and_then(|c: matrix_sdk::ruma::events::room::message::RoomMessageEventContent| {
c.body().map(|s| s.to_string())
})
.unwrap_or_default();
// Check if this is an edit
let is_edit = event
.as_original()
.map(|e| {
e.content()
.get("m.relates_to")
.and_then(|v| v.as_object())
.and_then(|o| o.get("rel_type"))
.and_then(|v| v.as_str())
== Some("m.replace")
})
.unwrap_or(false);
cx.render(rsx! {
div {
class: "matrix-chat-event",
style: "margin: 14px 16px; padding-bottom: 10px; border-bottom: 1px solid var(--background-modifier-border);",
div {
class: "matrix-chat-msghead",
style: "font-size: 1em; font-weight: 500; color: var(--interactive-accent); display: flex; align-items: center; gap: 10px;",
span { display_name }
span {
style: "font-size: 0.85em; color: var(--text-muted); margin-left: auto;",
{time_str.clone()}
if is_edit { " · edited" }
}
}
div {
class: "matrix-chat-msgbody",
style: "margin: 6px 0 0 0; font-size: 1em;",
{content}
}
}
})
}
/// Editor component - message input area
#[component]
pub fn Editor(cx: Scope, mize: Mize, draft: UseSignal<String>, edit_id: UseSignal<Option<String>>) -> Element {
let on_input = move |evt: FormEvent| {
draft.set(evt.value.clone());
};
let on_send = move |_| {
let mize_clone = mize.clone();
let draft_val = draft.read().clone();
let edit_id_val = edit_id.read().clone();
cx.spawn(async move {
if let Ok(mut matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
matrix_part.message_draft = draft_val;
matrix_part.edit_event_id = edit_id_val;
if let Err(e) = matrix_part.send_message().await {
console_log!("Failed to send message: {:?}", e);
}
// Clear local draft
draft.set(String::new());
edit_id.set(None);
}
});
};
let on_keydown = move |evt: dioxus_web::events::KeyboardEvent| {
if (evt.ctrl_key() || evt.meta_key()) && evt.key() == "Enter" {
on_send(evt);
}
};
let on_cancel = move |_| {
draft.set(String::new());
edit_id.set(None);
};
cx.render(rsx! {
div {
class: "matrix-chat-editor",
style: "border-top: 1px solid var(--background-modifier-border); background: var(--background-primary); padding: 8px 12px;",
div {
class: "matrix-chat-editor-inner",
style: "border: 1px solid var(--background-modifier-border); border-radius: 6px; overflow: hidden; background: var(--background-secondary);",
textarea {
r#type: "text",
rows: "3",
style: "width: 99%; min-height: 40px; resize: vertical; background: transparent; border: none; outline: none; padding: 8px; font-family: inherit; font-size: inherit; color: var(--text-normal);",
value: "{draft}",
oninput: on_input,
onkeydown: on_keydown,
}
}
button {
r#type: "button",
style: "margin-top: 3px; float: right; padding: 6px 14px; font-size: 0.97em; border-radius: 5px; border: none; background: var(--interactive-accent); color: white; cursor: pointer;",
onclick: on_send,
if edit_id.read().is_some() { "Save" } else { "Send" }
}
if edit_id.read().is_some() {
button {
r#type: "button",
style: "margin-right: 12px; padding: 6px 12px; font-size: 0.93em; border-radius: 5px; border: none; color: var(--text-normal); background: var(--background-secondary); cursor: pointer;",
onclick: on_cancel,
"Cancel"
}
}
}
})
}
/// Timeline component - list of messages
#[component]
pub fn Timeline(cx: Scope, mize: Mize, timeline: UseSignal<Vec<SyncRoomEvent>>) -> Element {
// Periodically refresh timeline from MatrixPart
cx.use_effect((), move |_| {
let mize_clone = mize.clone();
let timeline_clone = timeline.clone();
// Set up interval to poll for updates
let interval = js_sys::set_interval(
&Closure::<dyn FnMut()>::wrap(Box::new(move || {
if let Ok(matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
*timeline_clone.write() = matrix_part.timeline.clone();
}
})),
1000, // 1 second
);
// Cleanup on unmount
move || {
js_sys::clear_interval(interval);
}
});
cx.render(rsx! {
div {
class: "matrix-chat-scroll",
style: "flex-grow: 1; overflow-y: auto; padding: 8px 0; background: var(--background-secondary);",
// Room name header
div {
class: "matrix-chat-roomname",
style: "font-weight: 600; text-align: center; font-size: 1.1em; margin: 2px 0 12px 0; border-bottom: 1px solid var(--background-modifier-border); color: var(--text-accent); background: var(--background-primary);",
"Matrix Chat - {ROOM_ID}"
}
// Messages
for event in timeline.read().iter() {
rsx! { Message { event: event.clone() } }
}
}
})
}
/// Root chat component
#[component]
pub fn Chat(cx: Scope, props: ChatProps) -> Element {
let timeline = use_signal(&cx, || props.initial_timeline);
let draft = use_signal(&cx, || props.initial_draft);
let edit_id = use_signal(&cx, || props.initial_edit_id);
cx.render(rsx! {
div {
class: "matrix-chat-root",
style: "height: 100%; display: flex; flex-direction: column; background: var(--background-primary); color: var(--text-normal);",
// Title
div {
class: "matrix-chat-title",
style: "padding: 12px; font-weight: bold; font-size: 1.2em; text-align: center; border-bottom: 1px solid var(--background-modifier-border);",
"Matrix Chat"
}
// Info
div {
class: "matrix-chat-info",
style: "font-size: 0.85em; color: var(--text-muted); text-align: center; margin-bottom: 5px;",
"Room: {ROOM_ID}"
}
// Timeline
Timeline { mize: props.mize.clone(), timeline }
// Editor
Editor { mize: props.mize.clone(), draft, edit_id }
}
})
}
/// Mount the Dioxus app into a DOM element
pub fn mount_chat_app(
element: web_sys::HtmlElement,
initial_timeline: Vec<SyncRoomEvent>,
initial_draft: String,
initial_edit_id: Option<String>,
mize: Mize,
) {
dioxus_web::launch(
dioxus::prelude::App::new(move || rsx! {
Chat {
mize: mize.clone(),
initial_timeline: initial_timeline.clone(),
initial_draft: initial_draft.clone(),
initial_edit_id: initial_edit_id.clone(),
}
}),
element,
);
}
// Console log macro
#[macro_export]
macro_rules! console_log {
($($t:tt)*) => {
web_sys::console::log_1(&format!($($t)*).into());
};
}

View File

@@ -1,133 +0,0 @@
use mize::{Mize, MizeResult, MizePart, MizePartCreate, mize_part};
use wasm_bindgen::JsValue;
use mize::error::IntoMizeResultJsValue;
use wasm_bindgen::prelude::*;
use dioxus::prelude::*;
use crate::ClientAuth;
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use mize::platform::js::JsMize;
// console_log macro
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
}
pub fn part_obsidian(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
mize.register_part(Box::new(ObsidianPart {
mize: mize.clone(),
plugin,
app,
}));
Ok(())
}
#[mize_part("obsidian")]
#[derive(Default)]
pub struct ObsidianPart {
mize: Mize,
plugin: JsValue, // the jsValue of the obsidian plugin
app: JsValue, // the jsValue of the obsidian instance
}
pub struct MartsCommand {
pub id: String,
pub name: String,
pub callback: Box<dyn FnMut(Mize) -> MizeResult<()>>,
}
impl MizePart for ObsidianPart {}
impl MizePartCreate for ObsidianPart {}
impl ObsidianPart {
pub fn plugin(&self) -> &JsValue {
&self.plugin
}
pub fn add_command(&self, mut cmd: MartsCommand) -> MizeResult<()> {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use js_sys::{Function, Reflect, Object};
let obsidian_cmd = Object::new();
Reflect::set(&obsidian_cmd, &"id".into(), &"login".into()).mres()?;
Reflect::set(&obsidian_cmd, &"name".into(), &"Login".into()).mres()?;
// callback
let mize_clone = self.mize.clone();
let cb = Closure::<dyn FnMut()>::wrap(Box::new(move || {
if let Err(e) = (cmd.callback)(mize_clone.clone()) {
mize_clone.report_err(e);
}
}));
Reflect::set(&obsidian_cmd, &"callback".into(), cb.as_ref()).mres()?;
// plugin.addCommand(cmd)
let add_command_fn = Reflect::get(&self.plugin, &"addCommand".into()).mres()?
.dyn_into::<Function>().mres()?;
add_command_fn
.call1(&self.plugin, &obsidian_cmd).mres()?;
// IMPORTANT: prevent callback from being dropped
cb.forget();
Ok(())
}
}
#[wasm_bindgen]
pub fn test_fn(mize: &mut JsMize) {
let mize = mize.inner();
console_log!("test fn...");
if let Err(e) = try_test_fn(mize) {
console_log!("err: {:?}", e);
}
}
fn try_test_fn(mize: &mut Mize) -> MizeResult<()> {
//let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
//console_log!("secrets: {:?}", auth.secrets());
let parts = mize.parts.lock().unwrap();
for part in parts.keys() {
console_log!("part: {}", part)
}
Ok(())
}
#[wasm_bindgen]
pub fn part_obsidian_init(mize: &mut JsMize, plugin: JsValue, app: JsValue) {
let mut mize = mize.inner();
if let Err(e) = obsidian_mize_main(&mut mize, plugin, app) {
console_log!("Failed to start the obsidian mize part: {:?}", e);
}
}
fn obsidian_mize_main(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
crate::part_obsidian(mize, plugin, app)?;
crate::part_client_auth(mize)?;
crate::spacetime(mize)?;
crate::part_matrix(mize)?;
Ok(())
}

View File

@@ -1,199 +0,0 @@
use std::str::FromStr;
use axum::response::Html;
use clap::Command;
use dioxus::prelude::*;
use mize::Mize;
use mize::MizeResult;
pub mod server;
pub fn mc_page() -> Html<String> {
Html(r#"
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="style.css">
<title>PPC Minecraft Server</title>
<script defer data-domain="ppc.social" src="https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #f0efeb;
height: 100vh;
width: 100vw;
font-family: 'Montserrat', sans-serif;
padding: 100px;
font-size: 16px;
font-weight: 800;
color: #8e8e8e;
}
.strong { color: black; }
.semi { color: #484847; }
.link { color: #588fff; display: inline; }
.code { font-family: monospace; }
.mt { margin-top: 20px; }
.ml { margin-left: 20px; }
.mr { margin-right: 20px; }
.mb { margin-bottom: 20px; }
.flex {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.main {
width: 100%;
row-gap: 150px;
}
.card {
padding: 20px;
flex: 1 0 400px;
& > * {
display: block;
margin-top: 5px;
.card-heading { font-size: 24px; }
.card-body {
text-align: justify;
}
}
}
.title {
font-size: 48px;
flex: 1 0 40%;
}
.info { flex: 1 0 60%; }
.sub-card {
flex: 1 1 50%;
height: min-content;
padding: 20px 20px 20px 0;
* {
display: block;
margin-top: 5px;
}
.card-heading { font-size: 18px; }
.card-footer { font-size: 12px; }
.card-body {
font-size: 23px;
color: #588fff;
}
}
@media screen and (max-width: 650px) {
* { word-wrap: normal; }
body { padding: 20px; }
.title { font-size: 24px; }
.card { padding: 0; flex: 0 0 100%; }
.card:not(:last-child):after {
display: block;
content: "";
border-bottom: 5px solid currentColor;
margin: 20px 0 20px 0;
border-radius: 5px;
}
.card:last-child { margin-bottom: 100px; }
.sub-card:after { display: block; content: ""; border-bottom: 1px solid currentColor; margin-top: 20px; }
.main { row-gap: 0; }
.sub-card .card-body { font-size: 18px; }
}
@media screen and (max-width: 360px) {
body { font-size: 12px !important; }
.sub-card .card-body { font-size: 16px; }
}
</style>
</head>
<body>
<div class="main flex">
<div class="card title">
<div>Welcome to the</div>
<div class="strong">PPC</div>
<div>Minecraft Cluster</div>
</div>
<div class="card info flex">
<div class="sub-card">
<span class="card-heading">Address</span>
<span class="card-body">first.ppc.social</span>
<span class="card-footer">Java 1.21.11 and Bedrock (bedrock not working rn)</span>
</div>
<div class="sub-card">
<span class="card-heading">Discord</span>
<a class="card-body" href="https://discord.gg/cMKQNJMkRe" target="_blank">https://discord.gg/cMKQNJMkRe</a>
<span class="card-footer">come and say hi!</span>
</div>
<div class="semi mt">
See you ingame and invite all your friends to play as well!!
</div>
</div>
<div class="card">
<span class="card-heading strong">SimpleVoicechat</span>
<span class="card-body">
This is <span class="semi">not required</span>, but makes playing soooo much more fun, so it's
<span class="semi">highly encouraged</span>.
<br/><br/>
<span class="semi">Instructions can be found here</span>:<br/>
<a href="https://fabricmc.net/use/installer/" class="link ml">Fabric</a>
<br/>
<a class="link ml" href="https://modrepo.de/minecraft/voicechat/wiki/installation#fabric">SimpleVoiceChat</a>
<br/><br/>
</span>
</div>
</div>
</body>
</html>
"#.to_owned())
}
pub fn main_page() -> Html<String> {
Html(r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>People Corner</title>
<link rel="stylesheet" href="/app/static/styles.css">
<link rel="icon" type="image/png" href="/app/static/logo.png">
<script defer data-domain="ppc.social" src="https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
</head>
<body style="text-align: center" >
<div class="highlighted">
<h2>"Cool People doing Cool things together"</h2>
<p>Read what this is all about: <a href="https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC">About</a></p>
<br>
<p>There is a Discord Server: <a href="https://discord.gg/cMKQNJMkRe">https://discord.gg/cMKQNJMkRe</a></p>
<p>There is a <a href="https://ppc.social/mc">Minecraft server</a>.</p>
<p>There is <a href="https://wiki.ppc.social">The PPC Wiki Projekt</a>.</p>
<p>There is an <a href="https://www.instagram.com/ppc.social/">Instagram</a> and a <a href="https://www.youtube.com/@ppc.social">Youtube Channel</a></p>
<p>and that's about all for now...</p>
</body>
</html>
"#.to_owned())
}

View File

@@ -1,146 +0,0 @@
use std::str::FromStr;
use mize::{Mize, MizeError, MizeResult, mize_err};
use axum::response::Html;
use serde_json::json;
use serde_json::Value;
use axum::{
Json,
Router,
error_handling::HandleErrorLayer,
http::Uri,
response::IntoResponse,
routing::{any, get},
};
use axum_oidc::handle_oidc_redirect;
use axum_oidc::{
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
OidcRpInitiatedLogout, error::MiddlewareError,
};
use openidconnect::Scope;
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
use tower::ServiceBuilder;
use tower_sessions::{
Expiry, MemoryStore, SessionManagerLayer,
cookie::{SameSite, time::Duration},
};
pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> {
mize.add_name_only_part("ppc.server");
let mize_clone = mize.clone();
mize.spawn_async("ppc.server_runner", start_server(mize_clone));
Ok(())
}
pub async fn start_server(mut mize: Mize) -> MizeResult<()> {
dioxus::logger::initialize_default();
let issuer = mize.get_config("auth.server.issuer")?.value_string()?;
let client_id = mize.get_config("auth.server.client_id")?.value_string()?;
let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?;
let url = mize.get_config("web.url")?.value_string()?;
let redirect_url = format!("{url}/oidc");
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false)
.with_same_site(SameSite::Lax)
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
let oidc_login_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
.with_default_http_client()
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
.with_client_id(ClientId::new(client_id))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("email".into()))
// Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected.
//.add_untrusted_audience(Audience::new("123456789".to_string()))
.with_client_secret(ClientSecret::new(client_secret))
.discover(IssuerUrl::new(issuer.into()).expect("Invalid IssuerUrl"))
.await
.unwrap()
.build();
let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcAuthLayer::new(oidc_client));
let app = Router::new()
.route("/foo", get(authenticated))
.route("/logout", get(logout))
.layer(oidc_login_service)
.route("/bar", get(maybe_authenticated))
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
.layer(oidc_auth_service)
.layer(session_layer)
.layer(tower_http::trace::TraceLayer::new_for_http())
// not authenticated routes
.route("/", get(crate::ppc::main_page()))
.route("/mc", get(crate::ppc::mc_page()))
// matrix .well-known
.route(
"/.well-known/matrix/client",
get(handle_matrix_client).layer(tower_http::cors::CorsLayer::permissive()),
)
.route(
"/.well-known/matrix/server",
get(handle_matrix_server),
);
tracing::info!("Running on http://localhost:3000");
let listener = tokio::net::TcpListener::bind("0.0.0.0:8010").await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
format!("Hello {}", claims.subject().as_str())
}
async fn maybe_authenticated(
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
) -> impl IntoResponse {
if let Ok(claims) = claims {
format!(
"Hello {}! You are already logged in from another Handler.",
claims.subject().as_str()
)
} else {
"Hello anon!".to_string()
}
}
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
}
async fn handle_matrix_client() -> Json<Value> {
Json(json!({
"m.homeserver": {
"base_url": "https://matrix.ppc.social"
}
}))
}
async fn handle_matrix_server() -> Json<Value> {
Json(json!({
"m.server": "matrix.ppc.social:443"
}))
}

View File

@@ -1,8 +0,0 @@
pub use mize::{Mize, MizeResult, MizeError};
pub fn part_sci_inbox(mize: &mut Mize) -> MizeResult<()> {
mize.add_name_only_part("sci.inbox");
Ok(())
}

View File

@@ -1,31 +0,0 @@
use mize::*;
pub mod timetrack;
use clap::Command;
pub fn part_sci(mize: &mut Mize) -> MizeResult<()> {
part_add_thought(mize)?;
Ok(())
}
pub fn part_add_thought(mize: &mut Mize) -> MizeResult<()> {
use crate::generated::spacetime::add_thought;
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("sci");
cli.subcommand(Command::new("inputThought").alias("ti"), |_matches, mut mize| {
let tmp_file = std::env::temp_dir().join(format!(
"sc2-input-thought-{}.txt",
chrono::Utc::now().timestamp()
));
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
let new_file_content = 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 spacetime = mize.get_part_native::<crate::SpacetimePart>("spacetime")?;
spacetime.con().reducers.add_thought(new_file_content)?;
Ok(())
})?;
Ok(())
}

View File

@@ -1,640 +0,0 @@
use std::sync::oneshot;
#[cfg(feature = "target-os")]
use clap::{Arg, ArgAction, ArgMatches, Command};
use generated::spacetime::{Profile, TaskPart};
use mize::out;
use mize::proto::MizeMessage;
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
use spacetimedb_sdk::{DbContext, Table};
use generated::spacetime::export_timetrack;
use generated::spacetime::first_timetrack_date;
use std::fs::OpenOptions;
use chrono::Local;
use chrono::TimeDelta;
use std::io::Write;
use std::path::Path;
use std::fs;
use std::io;
use generated::spacetime::{
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
update_activity, update_upcoming,
};
use crate::spacetime::SpacetimePart;
use spacetimedb_sdk::TableWithPrimaryKey;
#[mize_part]
#[derive(Default)]
pub struct TimeTrack {
mize: Mize,
}
pub fn part_timetrack(mize: &mut Mize) -> MizeResult<()> {
part_timetrack_export(mize)?;
#[cfg(feature = "target-os")] {
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("sci");
cli.subcommand(
Command::new("editActionList").alias("aal").arg(
Arg::new("defaultTemplate")
.short('d')
.action(ArgAction::SetTrue),
),
command_edit_action_list,
)?;
cli.subcommand(Command::new("nextAction").alias("ne").arg(
Arg::new("break")
.short('b')
.action(ArgAction::SetTrue),
).arg(
Arg::new("action")
.trailing_var_arg(true)
.allow_hyphen_values(true)
.num_args(0..), // Accepts 0 or more arguments
), command_next_action)?;
cli.subcommand(
Command::new("printCurrentAction").alias("cu"),
command_current_action,
)?;
cli.subcommand(
Command::new("listActions").alias("la"),
command_list_actions,
)?;
cli.subcommand(
Command::new("listActivities").alias("lac"),
command_list_activities,
)?;
cli.subcommand(
Command::new("editActivities").alias("eac"),
command_edit_activities,
)?;
mize.add_listener(
"timetrack.sub_current",
Box::new(timetrack_sub_current_listener),
)?;
}
mize.register_part(Box::new(TimeTrack { mize: mize.clone() }))?;
Ok(())
}
fn send_current_update_msg(conn_id: u64, data: TaskPart, mize: &mut Mize) -> MizeResult<()> {
let now = fixed_len(data.current.name.clone(), 48);
let next = fixed_len(
data.upcoming
.iter()
.nth(0)
.unwrap_or(&Action {
name: "".to_string(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
})
.name
.clone(),
48,
);
let msg = MizeMessage::new_custom(
"timetrack.current",
format!(" {} NEXT: {} ", now, next),
conn_id,
);
let con = mize.get_connection(conn_id)?;
con.send(msg)?;
Ok(())
}
fn fixed_len(mut buf: String, len: usize) -> String {
if buf.len() > len {
buf.truncate(len - 4); // Cut off extra characters
buf.push_str(" ...");
} else {
while buf.len() < len {
buf.push(' '); // Fill with whitespace
}
}
buf
}
pub fn timetrack_sub_current_listener(msg: MizeMessage, mize: &mut Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
let mut mize_clone = mize.clone();
let mut mize_clone_two = mize.clone();
con.subscription_builder()
.on_applied(move |ctx| {
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
if let Err(e) = send_current_update_msg(msg.conn_id, data, &mut mize_clone) {
e.log();
}
})
.add_query(|q| q.from.task_part())
.subscribe();
con.db
.task_part()
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
if let Err(e) = send_current_update_msg(msg.conn_id, new.clone(), &mut mize_clone_two) {
e.log();
}
});
Ok(())
}
impl MizePartCreate for TimeTrack {}
impl MizePart for TimeTrack {
fn name(&self) -> &'static str {
"timetrack"
}
}
#[cfg(feature = "target-os")]
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let con = spacetime.con();
let break_flag = matches.get_flag("break");
let action = matches.get_many::<String>("action")
.map(|vals| vals.map(|s| s.as_str()).collect::<Vec<&str>>().join(" "));
let action = match action {
Some(action) => Some(Action {
name: action.clone(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
}),
None => None,
};
let old_action = get_current_action(&mut mize, con)?;
out!("before action: {}", old_action.name);
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
con.reducers.next_action_then(break_flag, action, |_, _| {
wait_tx.send(()).unwrap();
})?;
waiter_rx.recv()?;
let new_action = get_current_action(&mut mize, con)?;
out!("new action: {}", new_action.name);
Ok(())
}
#[cfg(feature = "target-os")]
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let action = get_current_action(&mut mize, spacetime.con())?;
out!("{}", action.name);
Ok(())
}
fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Action> {
let (wait_tx, wait_rx) = std::sync::oneshot::channel::<Action>();
con.subscription_builder()
.on_applied(|ctx| {
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
if task_part_data.is_none() {
wait_tx
.send(Action {
name: "fillier".to_string(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
})
.unwrap();
} else {
wait_tx.send(task_part_data.unwrap().current).unwrap();
}
})
.add_query(|q| {
q.from.task_part()
//.r#where(|row| row.identity.eq(con.identity()))
})
.subscribe();
let action = wait_rx.recv().unwrap();
Ok(action)
}
#[cfg(feature = "target-os")]
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let activities = get_activities(&mut mize, spacetime.con())?;
for ac in activities.clone() {
print!("ac {}: {}", ac.id, ac.name);
if !ac.parents_ids.is_empty() {
print!(" -");
}
for child_id in ac.parents_ids {
let child_ac = activities.iter().find(|ac| ac.id == child_id).unwrap();
print!(" {}", child_ac.name);
}
print!("\n")
}
Ok(())
}
#[cfg(feature = "target-os")]
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let actions = get_actions(spacetime.con())?;
for action in actions {
out!("{}", action.name)
}
Ok(())
}
#[cfg(feature = "target-os")]
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
// maybe append a template to the action list
if matches.get_flag("defaultTemplate") {
out!("Appending template...");
return Ok(());
}
// get list of actions
let mut actions = get_actions(spacetime.con())?;
// write to tmp file
let tmp_file_content = actions_to_string(&actions)?;
let tmp_file = std::env::temp_dir().join(format!(
"ppc-edit-actions-{}.txt",
chrono::Utc::now().timestamp()
));
std::fs::write(&tmp_file, tmp_file_content)
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
// open $EDITOR blocking
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
// read file and update db
let new_file_content = std::fs::read_to_string(&tmp_file)
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
actions = actions_from_string(new_file_content)?;
set_actions(actions, spacetime.con())?;
// delete tmpfile
let _ = std::fs::remove_file(&tmp_file);
Ok(())
}
fn get_actions(con: &DbConnection) -> MizeResult<Vec<Action>> {
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Action>>();
con.subscription_builder()
.on_applied(|ctx| {
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
if task_part_data.is_none() {
exit_tx.send(Vec::new()).unwrap();
} else {
exit_tx.send(task_part_data.unwrap().upcoming).unwrap();
}
})
.add_query(|q| {
q.from.task_part()
//.r#where(|row| row.identity.eq(con.identity()))
})
.subscribe();
let activities = exit_rx.recv().unwrap();
Ok(activities)
}
fn set_actions(actions: Vec<Action>, con: &DbConnection) -> MizeResult<()> {
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
con.reducers
.update_upcoming_then(actions, |_, _| wait_tx.send(()).unwrap())?;
waiter_rx.recv().unwrap();
Ok(())
}
fn actions_to_string(actions: &Vec<Action>) -> MizeResult<String> {
let mut string = String::new();
for action in actions.iter() {
string.push_str(format!("{}\n", action.name).as_str());
}
Ok(string)
}
fn actions_from_string(string: String) -> MizeResult<Vec<Action>> {
let mut new_actions = Vec::new();
for line in string.lines() {
if line.trim().is_empty() {
continue;
}
let mut action = Action {
name: line.to_owned(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
};
parse_action(&mut action);
new_actions.push(action);
}
Ok(new_actions)
}
fn parse_action(action: &mut Action) {
let _parts: Vec<&str> = action.name.split_whitespace().collect();
}
#[cfg(feature = "target-os")]
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
// get list of activities
let activities = get_activities(&mut mize, spacetime.con())?;
let con = spacetime.con();
// write to tmp file
let mut tmp_file_content = String::new();
for ac in activities.iter() {
tmp_file_content.push_str(format!("{} {}", ac.id, ac.name).as_str());
if !ac.parents_ids.is_empty() {
tmp_file_content.push_str(" -");
for parent_id in ac.parents_ids.iter() {
let parent_ac = activities.iter().find(|ac| ac.id == *parent_id).unwrap();
tmp_file_content.push_str(" ");
tmp_file_content.push_str(&parent_ac.name);
}
}
tmp_file_content.push_str("\n");
}
let tmp_file = std::env::temp_dir().join(format!(
"ppc-edit-activities-{}.txt",
chrono::Utc::now().timestamp()
));
std::fs::write(&tmp_file, tmp_file_content)
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
loop {
// open $EDITOR blocking
crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?;
let new_file_content = std::fs::read_to_string(&tmp_file)
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
let mut input = String::new();
let old_activities = get_activities(&mut mize, con)?;
if let Err(err) =
parse_activities_from_text(&mut mize, con, new_file_content, old_activities)
{
out!("error parsing the activities from the file: {:?}", err);
out!("press ENTER to open the file again");
// wait for user input
// so that they can se the err msg, before running nvim again
std::io::stdin().read_line(&mut input)?;
} else {
break;
}
}
// delete tmpfile
let _ = std::fs::remove_file(&tmp_file);
Ok(())
}
fn get_activities(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Vec<Activity>> {
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Activity>>();
con.subscription_builder()
.on_applied(|ctx| {
exit_tx.send(ctx.db.activities().iter().collect()).unwrap();
})
.add_query(|q| q.from.activities())
.subscribe();
let activities = exit_rx.recv().unwrap();
Ok(activities)
}
fn parse_activities_from_text(
mize: &mut Mize,
con: &DbConnection,
text: String,
old_activities: Vec<Activity>,
) -> MizeResult<()> {
let mut activities_to_delete = old_activities.clone();
let mut wait_revievers = Vec::new();
for line in text.lines() {
if line == "" {
continue;
}
// split
let mut parts = line.split(" ");
let id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
// remove from to-delete list
if let Some(pos) = activities_to_delete.iter().position(|ac| ac.id == id) {
activities_to_delete.remove(pos);
}
// wait rx
let (wait_tx, wait_rx) = oneshot::channel::<()>();
wait_revievers.push(wait_rx);
// update or create activity
if let Some(old_activity) = old_activities.iter().find(|ac| ac.id == id) {
con.reducers.update_activity_then(
Activity {
id,
name,
task: old_activity.task,
parents_ids: old_activity.parents_ids.clone(),
},
move |_, _| {
let _ = wait_tx.send(());
},
)?;
} else {
con.reducers.update_activity_then(
Activity {
id: 0,
name,
task: None,
parents_ids: Vec::new(),
},
move |_, _| {
let _ = wait_tx.send(());
},
)?;
}
}
// remove deleted activities
for activity in activities_to_delete {
let (wait_tx, wait_rx) = oneshot::channel::<()>();
wait_revievers.push(wait_rx);
con.reducers.remove_activity_then(activity, move |_, _| {
let _ = wait_tx.send(());
})?;
}
// wait for db stuff to finish
for wait_rx in wait_revievers {
wait_rx.recv().unwrap();
}
let mut wait_revievers = Vec::new();
// check for declared parents
let new_activities = get_activities(mize, con)?;
out!("new_activities: {:?}", new_activities);
for line in text.lines() {
if line == "" {
continue;
}
// split
let mut parts = line.split(" ");
let _id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
// parse parents
let seperator = parts.next();
if seperator.is_some() && seperator.unwrap() == "-" {
let mut parents_ids = Vec::new();
for parent_name in parts {
// find id from old_parts by this name
let res_from_old = old_activities.iter().find(|ac| ac.name == parent_name);
let res_from_new = new_activities.iter().find(|ac| ac.name == parent_name);
let parent_activity = res_from_old.or(res_from_new).ok_or(mize_err!(
"activity '{name}' declares parent '{parent_name}'"
))?;
parents_ids.push(parent_activity.id);
}
let child_activity = new_activities
.iter()
.find(|ac| ac.name == name)
.ok_or(mize_err!(".."))?;
let (wait_tx, wait_rx) = oneshot::channel::<()>();
wait_revievers.push(wait_rx);
con.reducers.update_activity_then(
Activity {
parents_ids,
..child_activity.to_owned()
},
move |_, _| {
let _ = wait_tx.send(());
},
)?;
}
}
// wait for db stuff to finish
for wait_rx in wait_revievers {
wait_rx.recv().unwrap();
}
Ok(())
}
pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("sci");
cli.subcommand(Command::new("timetrackExport").alias("te").arg(Arg::new("date").short('d')).arg(Arg::new("all").short('a').action(ArgAction::SetTrue)), |matches , mut mize| {
let spacetime = mize.get_part_native::<crate::SpacetimePart>("spacetime")?;
let today = Local::now().format("%Y-%m-%d").to_string();
let mut date = matches.get_one::<String>("date").unwrap_or(&today);
let all = matches.get_flag("all");
if all {
out!("exporting all not implemented rn");
let (tx, rx) = oneshot::channel();
spacetime.con().procedures.first_timetrack_date_then("c2vi".to_string(), |_, res| {tx.send(res);});
let res = rx.recv()???;
let dates = dates_until_today(&res)?;
for date in dates {
out!("date: {}", date);
let (tx, rx) = oneshot::channel();
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);});
let res = rx.recv()???;
//let mut file = OpenOptions::new().create(true).write(true).open(Path::new(&notes_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?;
//file.write_all(res.as_bytes())?;
}
return Ok(());
}
out!("date: {}", date);
let (tx, rx) = oneshot::channel();
spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {let _ = tx.send(res);});
let res = rx.recv()???;
let daily_notes_path = mize.get_config("timetrack.dailyNotesPath")?.value_string()?;
let note_path = format!("{}/{}.md", daily_notes_path, date);
out!("writing to file: {}", note_path);
write_under_heading(note_path, "## timetrack export", res.as_ref())?;
Ok(())
})?;
Ok(())
}
fn write_under_heading(
file_path: impl AsRef<Path>,
heading: &str,
content: &str,
) -> io::Result<()> {
let file_path = file_path.as_ref();
let markdown = fs::read_to_string(file_path)?;
let heading_line = format!("{}", heading);
let mut lines: Vec<&str> = markdown.lines().collect();
let index = lines
.iter()
.position(|line: &&str| line.trim() == heading_line)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!("Heading '{}' not found", heading),
)
})?;
lines.insert(index + 1, content);
let mut updated = lines.join("\n");
updated.push('\n');
fs::write(file_path, updated)
}
fn dates_until_today(start: &str) -> Result<Vec<String>, chrono::ParseError> {
use chrono::NaiveDate;
let mut current = NaiveDate::parse_from_str(start, "%Y-%m-%d")?;
let today = Local::now().date_naive();
let mut dates = Vec::new();
while current <= today {
dates.push(current.format("%Y-%m-%d").to_string());
current += TimeDelta::days(1);
}
Ok(dates)
}

View File

@@ -1,107 +0,0 @@
use std::sync::oneshot::Sender;
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
use crate::auth::client::ClientAuth;
use generated::spacetime::DbConnection;
#[mize_part]
#[derive(Default)]
pub struct SpacetimePart {
mize: Mize,
con: Option<DbConnection>,
}
impl MizePartCreate for SpacetimePart {}
impl MizePart for SpacetimePart {
fn name(&self) -> &'static str {
"spacetime"
}
}
impl SpacetimePart {
pub fn con_mut(&mut self) -> &mut DbConnection {
self.con.as_mut().unwrap()
}
pub fn con(&self) -> &DbConnection {
self.con.as_ref().unwrap()
}
// does not work, because i can't use the QueryBuilder type in my code.... it's private
/*
pub fn get<T>(
&self,
on_applied: impl FnOnce(&generated::spacetime::SubscriptionEventContext, Sender<T>)+ Send + 'static,
query: impl FnOnce(QueryBuilder)+ Send + 'static
) -> MizeResult<T> {
let (done_tx, done_rx) = std::sync::oneshot::channel::<T>();
use spacetimedb_sdk::DbContext;
self.con().subscription_builder().on_applied(move |ctx| {
on_applied(ctx, done_tx);
}).add_query(build);
}
*/
}
pub fn spacetime(mize: &mut Mize) -> MizeResult<()> {
let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
let db_url = mize.get_config("spacetime.url")?.value_string()?;
let con: Option<DbConnection>;
#[cfg(feature = "target-os")] {
con = Some(DbConnection::builder()
.with_token(Some(auth.id_token()?))
.with_database_name("mize")
.with_uri(db_url)
//.on_connect(callback)
//.on_disconnect(callback)
//.on_connect_error(callback)
//.with_compression(...)
.build()?);
con.as_ref().unwrap().run_threaded();
}
#[cfg(feature = "target-js")] {
let mut mize = mize.clone();
con = None;
wasm_bindgen_futures::spawn_local(async move {
let id_token = match auth.id_token() {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
let con = match DbConnection::builder()
.with_token(Some(id_token))
.with_database_name("mize")
.with_uri(db_url)
//.on_connect(callback)
//.on_disconnect(callback)
//.on_connect_error(callback)
//.with_compression(...)
.build().await {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
let mut spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
con.run_async();
spacetime.con = Some(con);
});
}
mize.register_part(Box::new(SpacetimePart {
mize: mize.clone(),
con: con,
}))
}

View File

View File

@@ -1,538 +0,0 @@
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { unified } from "unified";
import { visit } from "unist-util-visit";
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 = [];
const REMARK = unified()
.use(remarkParse)
.use(remarkFrontmatter)
.use(remarkGfm)
.use(remarkStringify, { bullet: "-" });
export function task_obsidian_otask_parse(mize: any) {
const obsidian_plugin = mize.get_part_native("obsidian");
obsidian_plugin.addCommand({
id: "full-parse",
name: "Parse all otask files into the db",
callback: async () => {
await full_parse();
},
});
}
async function full_parse() {
console.log("full parse.......................... yay");
let otasks = [];
OTASK_PARSE_QUEUE = [];
OTASK_LIST = [];
const root_file = app.vault.getAbstractFileByPath("task/my-projects.md");
const content = await app.vault.read(root_file);
const list = await getListUnderHeading(content, "root");
for (const entry of list) {
queue_otasks_from_list_item(entry, []);
}
// go through the queue of otaskParseTasks
// using a queue instead of recursive calls, to make it more debuggable...
do {
const task = OTASK_PARSE_QUEUE.shift();
dbg("task", task);
if (task.file) {
const otask = await otasks_from_file(
task.file,
task.otask_path,
task.priority,
);
dbg("otask", otask);
if (otask) {
OTASK_LIST.push(otask);
}
} else {
const otask = await otasks_from_list_item(
task.listItem,
task.otask_path,
task.priority,
);
dbg("otask", otask);
if (otask) {
OTASK_LIST.push(otask);
}
}
} while (OTASK_PARSE_QUEUE.length > 0);
console.log("otasks::", OTASK_LIST);
render_sub0_file(OTASK_LIST);
}
let OTASK_PARSE_QUEUE: any = [];
let OTASK_LIST = [];
let DBG = false;
function dbg() {
if (DBG) {
console.log(arguments);
}
}
function queue_otasks_from_list_item(listItem, otask_path, priority = 0) {
OTASK_PARSE_QUEUE.push({
listItem,
otask_path,
priority,
});
}
function queue_otasks_from_file(file, otask_path, priority = 0) {
OTASK_PARSE_QUEUE.push({
file,
otask_path,
priority,
});
}
// parsing helper functions
async function render_sub0_file(otasks) {
const sub0_file = app.vault.getAbstractFileByPath("private/sub0-tasks.md");
if (!(sub0_file instanceof TFile)) {
console.error(
"SystemC2: sub0_file not found:",
"private/sub0-tasks.md",
sub0_file,
);
return;
}
//render sub0
const sub0_otasks = otasks.filter(
(otask) => otask.priority == 0 && !otask.is_file,
);
console.log("sub0 otasks...", sub0_otasks);
let rendered_otasks_string = "";
for (const otask of sub0_otasks) {
const path_part = otask.path.length > 0 ? otask.path.join(": ") + ": " : "";
let content_part = otask.content ? otask.content + "\n" : "";
content_part = content_part
.split("\n")
.map((line) => "\t" + line)
.join("\n"); // add a \t in front of every otask.content line
rendered_otasks_string += `- ${path_part}${otask.longName}\n${content_part}\n`;
}
//console.log("newContent...", rendered_otasks_string)
await replaceHeading(sub0_file, "sub0", rendered_otasks_string);
//render sub-1
}
async function replaceHeading(
file: TFile,
heading: string,
newContent: string,
) {
const data = await this.app.vault.read(file);
// Parse the Markdown into an AST
const tree = REMARK.parse(data) as Root;
// Split new content into AST nodes
const newContentAst = REMARK.parse(newContent) as Root;
const children = tree.children;
const newChildren: Content[] = [];
let insideTarget = false;
let targetLevel = 0;
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (node.type === "heading") {
const headingNode = node as Heading;
const text = headingNode.children
.filter((c) => c.type === "text")
.map((c: any) => c.value)
.join("");
// If we encounter our target heading
if (text.trim() === heading) {
insideTarget = true;
targetLevel = headingNode.depth;
newChildren.push(node); // Keep the heading itself
// Insert new content right after
newChildren.push(...newContentAst.children);
continue;
}
// If we’re currently inside target section and see a heading of same or higher level
if (insideTarget && headingNode.depth <= targetLevel) {
insideTarget = false;
}
}
// If we’re not inside the target section, keep the node
if (!insideTarget) {
newChildren.push(node);
}
}
const newTree: Root = { ...tree, children: newChildren };
// Convert AST back to Markdown
const newMarkdown = await REMARK.stringify(newTree);
await this.app.vault.modify(file, newMarkdown);
}
async function otasks_from_list_item(listItem, otask_path, priority = 0) {
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,
);
}
}
}
//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
}
}
// 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);
}
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) {
dbg("SystemC2: action_from_list_item", listItem, otask_path);
let action = {};
let text = nodeToString(listItem.children[0]).slice(4);
if (text.startsWith("[")) {
text = text.slice(4);
}
action.name = text;
return action;
}
function check_otask_prop_from_list_item(listItem, otask) {
let text = nodeToString(listItem.children[0]);
for (const prop_name of OTASK_PROP_NAMES) {
if (text.startsWith(prop_name + ":")) {
const value = text.slice(prop_name.length + 2);
if (prop_name == "priority") {
// parse priority as int
otask[prop_name] = parseInt(value);
} else if (prop_name == "outcome") {
// add sub items to it
let fullText = REMARK.stringify(listItem).slice(prop_name.length + 4);
otask[prop_name] = fullText;
} else {
otask[prop_name] = value;
}
return true;
}
}
return false;
}
// Extract plain text from a node
function nodeToString(node: any): string {
let result = "";
visit(node, "text", (textNode: Text) => {
result += textNode.value;
});
return result.trim();
}
async function getListUnderHeading(
markdown: string,
headingText: string,
): string[] {
const tree = REMARK.parse(markdown);
//dbg("tree", tree)
let capture = false;
let items: string[] = [];
for (let i = 0; i < tree.children.length; i++) {
const node: Content = tree.children[i];
// Detect the target heading
if (
node.type === "heading" &&
nodeToString(node).toLowerCase() === headingText.toLowerCase()
) {
capture = true;
continue;
}
// Stop if another heading of the same or higher level comes
if (capture && node.type === "heading") {
break;
}
// Collect list items if capturing
if (capture && node.type === "list") {
for (const li of (node as List).children) {
if (li.type === "listItem") {
items.push(li);
}
}
}
}
return items;
}
function getListItemsBeforeFirstHeading(markdown: string) {
const tree = REMARK.parse(markdown);
let items = [];
for (const node of tree.children) {
if (node.type === "heading") {
// stop at first heading
break;
}
if (node.type === "list") {
for (const li of (node as List).children) {
if (li.type === "listItem") {
items.push(li);
}
}
}
}
return items;
}