omega restructuring + a bunch of changes i have made over the last weeks

This commit is contained in:
Sebastian Moser
2026-07-31 00:48:15 +02:00
parent c57849f748
commit b2ed9efff2
201 changed files with 4331 additions and 49073 deletions

View File

@@ -0,0 +1,16 @@
[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"

393
marts/spacetime/src/lib.rs Normal file
View File

@@ -0,0 +1,393 @@
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!("# export for {}\n\n", date));
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:%S");
let end = event.end
.to_chrono_date_time()
.map_err(|err| err.to_string())?
.with_timezone(&Europe::Vienna)
.format("%H:%M:%S");
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
export.push_str(line.as_str());
}
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 mut 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)
}