final db models wip

This commit is contained in:
timurgordon
2025-06-03 21:50:08 +03:00
parent 2a2d69dafb
commit abbed9a1a1
43 changed files with 2958 additions and 1410 deletions

View File

@@ -1,90 +1,362 @@
use rhai::{Engine, EvalAltResult, NativeCallContext, ImmutableString};
use rhai::plugin::*;
use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array};
use std::sync::Arc;
use std::mem;
use chrono::{DateTime, Utc};
use crate::db::Db;
use heromodels_core::BaseModelData;
use super::calendar::{Event, Attendee, Calendar, AttendanceStatus};
type RhaiEvent = Event;
type RhaiAttendee = Attendee;
type RhaiCalendar = Calendar;
use crate::db::hero::OurDB;
use super::calendar::{Calendar, Event, Attendee, AttendanceStatus};
use adapter_macros::{adapt_rhai_i64_input_fn, adapt_rhai_i64_input_method};
use adapter_macros::rhai_timestamp_helpers;
use crate::db::Collection;
// Helper function for get_all_calendars registration
fn new_calendar_rhai(name: String) -> Result<Calendar, Box<EvalAltResult>> {
Ok(Calendar::new(None, name))
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
))
)
}
fn new_event_rhai(
context: NativeCallContext,
title_rhai: ImmutableString,
start_time_ts: i64,
end_time_ts: i64,
) -> Result<Event, Box<EvalAltResult>> {
let start_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(start_time_ts)
.map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to convert start_time for Event: {}", e_str).into(),
context.position(),
)))?;
let end_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(end_time_ts)
.map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to convert end_time for Event: {}", e_str).into(),
context.position(),
)))?;
Ok(Event::new(title_rhai.to_string(), start_time, end_time))
// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers
fn val_from_i64_to_u64(val_i64: i64) -> Result<u64, Box<EvalAltResult>> {
u64::try_from(val_i64).map_err(|_|
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert value '{}' to u64", val_i64).into(),
Position::NONE
))
)
}
pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc<OurDB>) {
engine.register_fn("name", move |calendar: Calendar, name: String| Calendar::name(calendar, name));
engine.register_fn("description", move |calendar: Calendar, description: String| Calendar::description(calendar, description));
engine.register_fn("add_event", Calendar::add_event);
// Note: Event IDs are i64 in Calendar.events, but Event model's base_data.id is u32.
// This might require adjustment if events are fetched by ID from the DB via Calendar.events.
#[export_module]
mod rhai_calendar_module {
// --- Event Functions ---
#[rhai_fn(name = "new_event")]
pub fn new_event() -> RhaiEvent {
Event::new()
}
engine.register_fn("new_event", |context: NativeCallContext, title_rhai: ImmutableString, start_time_ts: i64, end_time_ts: i64| -> Result<Event, Box<EvalAltResult>> {
new_event_rhai(context, title_rhai, start_time_ts, end_time_ts)
});
engine.register_fn("title", move |event: Event, title: String| Event::title(event, title));
engine.register_fn("description", move |event: Event, description: String| Event::description(event, description));
engine.register_fn("add_attendee", move |event: Event, attendee: Attendee| Event::add_attendee(event, attendee));
engine.register_fn("remove_attendee", adapt_rhai_i64_input_method!(Event, remove_attendee, u32));
engine.register_fn("update_attendee_status", move |context: NativeCallContext, event: Event, contact_id_i64: i64, status: AttendanceStatus| -> Result<Event, Box<EvalAltResult>> {
let contact_id_u32: u32 = contact_id_i64.try_into().map_err(|_e| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Conversion error for contact_id in Event::update_attendee_status from i64 to u32"),
context.position(),
))
})?;
Ok(event.update_attendee_status(contact_id_u32, status))
});
engine.register_fn("new_attendee", adapt_rhai_i64_input_fn!(Attendee::new, u32));
engine.register_fn("new_calendar", |name: String| -> Result<Calendar, Box<EvalAltResult>> { new_calendar_rhai(name) });
// Register a function to get the database instance
engine.register_fn("get_db", move || db.clone());
/// Sets the event title
#[rhai_fn(name = "title", return_raw, global, pure)]
pub fn event_title(event: &mut RhaiEvent, title: String) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event);
*event = owned_event.title(title);
Ok(event.clone())
}
// Register getters for Calendar
engine.register_get("id", |c: &mut Calendar| -> Result<i64, Box<EvalAltResult>> { Ok(c.base_data.id as i64) });
engine.register_get("name", |c: &mut Calendar| -> Result<String, Box<EvalAltResult>> {
// println!("Rhai attempting to get Calendar.name: {}", c.name); // Debug print
Ok(c.name.clone())
});
engine.register_get("description", |c: &mut Calendar| -> Result<Option<String>, Box<EvalAltResult>> { Ok(c.description.clone()) });
/// Sets the event description
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn event_description(event: &mut RhaiEvent, description: String) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event);
*event = owned_event.description(description);
Ok(event.clone())
}
/// Sets the event location
#[rhai_fn(name = "location", return_raw, global, pure)]
pub fn event_location(event: &mut RhaiEvent, location: String) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event);
*event = owned_event.location(location);
Ok(event.clone())
}
// Register getter for Calendar.base_data
engine.register_get("base_data", |c: &mut Calendar| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(c.base_data.clone()) });
/// Adds an attendee to the event
#[rhai_fn(name = "add_attendee", return_raw, global, pure)]
pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result<RhaiEvent, Box<EvalAltResult>> {
// Use take to get ownership of the event
let owned_event = mem::take(event);
*event = owned_event.add_attendee(attendee);
Ok(event.clone())
}
// Register getters for BaseModelData
engine.register_get("id", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.id.into()) });
/// Reschedules the event with new start and end times
#[rhai_fn(name = "reschedule", return_raw, global, pure)]
pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result<RhaiEvent, Box<EvalAltResult>> {
// Validate timestamps
if new_end_time <= new_start_time {
return Err(Box::new(EvalAltResult::ErrorRuntime(
"End time must be after start time".into(),
Position::NONE
)));
}
// Use take to get ownership of the event
let owned_event = mem::take(event);
*event = owned_event.reschedule(new_start_time, new_end_time);
Ok(event.clone())
}
/// Updates an attendee's status in the event
#[rhai_fn(name = "update_attendee_status", return_raw, global, pure)]
pub fn event_update_attendee_status(event: &mut RhaiEvent, contact_id: i64, status_str: String) -> Result<RhaiEvent, Box<EvalAltResult>> {
let status_enum = AttendanceStatus::from_string(&status_str)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(),
Position::NONE
)))?;
// Use take to get ownership of the event
let owned_event = mem::take(event);
*event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum);
Ok(event.clone())
}
// Database interaction functions for Calendar are expected to be provided by #[rhai_model_export(..)] on the Calendar struct.
// Ensure that `db.rs` or similar correctly wires up the `OurDB` methods for these.
// Event Getters
#[rhai_fn(get = "id", pure)]
pub fn get_event_id(event: &mut RhaiEvent) -> i64 { event.base_data.id as i64 }
#[rhai_fn(get = "created_at", pure)]
pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { event.base_data.created_at }
#[rhai_fn(get = "modified_at", pure)]
pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { event.base_data.modified_at }
#[rhai_fn(get = "title", pure)]
pub fn get_event_title(event: &mut RhaiEvent) -> String { event.title.clone() }
#[rhai_fn(get = "description", pure)]
pub fn get_event_description(event: &mut RhaiEvent) -> Option<String> { event.description.clone() }
#[rhai_fn(get = "start_time", pure)]
pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { event.start_time }
#[rhai_fn(get = "end_time", pure)]
pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { event.end_time }
#[rhai_fn(get = "location", pure)]
pub fn get_event_location(event: &mut RhaiEvent) -> Option<String> { event.location.clone() }
#[rhai_fn(get = "attendees", pure)]
pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec<RhaiAttendee> { event.attendees.clone() }
// --- Attendee Functions ---
#[rhai_fn(name = "new_attendee")]
pub fn new_attendee() -> RhaiAttendee {
Attendee::new(0) // Default contact_id, will be set via builder
}
/// Sets the contact ID for an attendee
#[rhai_fn(name = "with_contact_id", return_raw, global, pure)]
pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result<RhaiAttendee, Box<EvalAltResult>> {
let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0);
let owned_attendee = mem::replace(attendee, Attendee::new(0));
*attendee = Attendee::new(new_contact_id);
attendee.status = owned_attendee.status;
Ok(attendee.clone())
}
/// Sets the status for an attendee
#[rhai_fn(name = "with_status", return_raw, global, pure)]
pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result<RhaiAttendee, Box<EvalAltResult>> {
let status_enum = AttendanceStatus::from_string(&status_str)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(),
Position::NONE
)))?;
let owned_attendee = mem::replace(attendee, Attendee::new(0));
*attendee = owned_attendee.status(status_enum);
Ok(attendee.clone())
}
// We now use with_status instead of update_attendee_status for consistency
// Attendee Getters
#[rhai_fn(get = "contact_id", pure)]
pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { attendee.contact_id as i64 }
#[rhai_fn(get = "status", pure)]
pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { attendee.status.to_string() }
// --- Calendar Functions ---
#[rhai_fn(name = "new_calendar", return_raw)]
pub fn new_calendar() -> Result<RhaiCalendar, Box<EvalAltResult>> {
let calendar = Calendar::new(None, "");
Ok(calendar)
}
/// Sets the calendar name
#[rhai_fn(name = "name", return_raw, global, pure)]
pub fn calendar_name(calendar: &mut RhaiCalendar, name: String) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = Calendar::new(Some(owned_calendar.base_data.id), name);
Ok(calendar.clone())
}
/// Sets the calendar description
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn calendar_description(calendar: &mut RhaiCalendar, description: String) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar);
let updated_calendar = owned_calendar.description(description);
*calendar = updated_calendar.clone();
Ok(updated_calendar)
}
#[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)]
pub fn calendar_add_event(calendar: &mut RhaiCalendar, event: RhaiEvent) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = owned_calendar.add_event(event.base_data.id as i64);
Ok(calendar.clone())
}
#[rhai_fn(name = "remove_event_from_calendar", return_raw)]
pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64);
Ok(())
}
// Calendar Getters
#[rhai_fn(get = "id", pure)]
pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.id as i64 }
#[rhai_fn(get = "name", pure)]
pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { calendar.name.clone() }
#[rhai_fn(get = "created_at", pure)]
pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.created_at }
#[rhai_fn(get = "modified_at", pure)]
pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.modified_at }
#[rhai_fn(get = "events", pure)]
pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec<i64> { calendar.events.clone() }
#[rhai_fn(get = "description", pure)]
pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option<String> { calendar.description.clone() }
// Calendar doesn't have an owner_id field in the current implementation
// pub fn get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 }
// Getters for Event
engine.register_get("id", |e: &mut Event| -> Result<i64, Box<EvalAltResult>> { Ok(e.base_data.id as i64) });
engine.register_get("title", |e: &mut Event| -> Result<String, Box<EvalAltResult>> { Ok(e.title.clone()) });
// Add more getters for Event fields as needed
}
pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally
let module = exported_module!(rhai_calendar_module);
engine.register_global_module(module.into());
// Create a module for database functions
let mut db_module = Module::new();
// Manually register database functions as they need to capture 'db'
let db_clone_set_event = db.clone();
db_module.set_native_fn("save_event", move |event: Event| -> Result<Event, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_set_event.set(&event)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?;
// Return the updated event with the correct ID
Ok(result.1)
});
// Manually register database functions as they need to capture 'db'
let db_clone_delete_event = db.clone();
db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_delete_event.collection::<Event>()
.expect("can open event collection")
.delete_by_id(event.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
let db_clone_get_event = db.clone();
db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result<Event, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly
db_clone_get_event.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE)))
});
let db_clone_set_calendar = db.clone();
db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_set_calendar.set(&calendar)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?;
// Return the updated calendar with the correct ID
Ok(result.1)
});
// Manually register database functions as they need to capture 'db'
let db_clone_delete_calendar = db.clone();
db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_delete_calendar.collection::<Calendar>()
.expect("can open calendar collection")
.delete_by_id(calendar.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
let db_clone_get_calendar = db.clone();
db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result<Calendar, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly
db_clone_get_calendar.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE)))
});
// Add list_calendars function to get all calendars
let db_clone_list_calendars = db.clone();
db_module.set_native_fn("list_calendars", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_calendars.collection::<Calendar>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get calendar collection: {:?}", e).into(),
Position::NONE
)))?;
let calendars = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all calendars: {:?}", e).into(),
Position::NONE
)))?;
let mut array = Array::new();
for calendar in calendars {
array.push(Dynamic::from(calendar));
}
Ok(Dynamic::from(array))
});
// Add list_events function to get all events
let db_clone_list_events = db.clone();
db_module.set_native_fn("list_events", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_events.collection::<Event>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get event collection: {:?}", e).into(),
Position::NONE
)))?;
let events = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all events: {:?}", e).into(),
Position::NONE
)))?;
let mut array = Array::new();
for event in events {
array.push(Dynamic::from(event));
}
Ok(Dynamic::from(array))
});
// Register the database module globally
engine.register_global_module(db_module.into());
println!("Successfully registered calendar Rhai module using export_module approach.");
}