feat: Enhance calendar example with user and attendee management
- Add user creation and management to the calendar example. - Integrate user IDs into attendees for improved data integrity. - Improve event manipulation by adding and removing attendees by ID. - Enhance calendar example to demonstrate event and calendar retrieval. - Enhance the calendar example with database storage for events. - Modify the calendar example to manage events by ID instead of title.
This commit is contained in:
		@@ -1,32 +1,50 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use heromodels_core::BaseModelData;
 | 
			
		||||
use heromodels_derive::model;
 | 
			
		||||
use rhai_autobind_macros::rhai_model_export;
 | 
			
		||||
use rhai::{CustomType, TypeBuilder};
 | 
			
		||||
use rhai_autobind_macros::rhai_model_export;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
/// Represents the status of an attendee for an event
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum AttendanceStatus {
 | 
			
		||||
    Accepted,
 | 
			
		||||
    Declined,
 | 
			
		||||
    Tentative,
 | 
			
		||||
    NoResponse,
 | 
			
		||||
    Accepted = 0,
 | 
			
		||||
    Declined = 1,
 | 
			
		||||
    Tentative = 2,
 | 
			
		||||
    NoResponse = 3,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents an attendee of an event
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
 | 
			
		||||
#[model]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub struct Attendee {
 | 
			
		||||
    /// Base model data
 | 
			
		||||
    pub base_data: BaseModelData,
 | 
			
		||||
    /// ID of the user attending
 | 
			
		||||
    // Assuming user_id might be queryable
 | 
			
		||||
    pub contact_id: u32,
 | 
			
		||||
    /// Attendance status of the user for the event
 | 
			
		||||
    pub status: AttendanceStatus,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Attendee {
 | 
			
		||||
    /// Creates a new attendee with auto-generated ID
 | 
			
		||||
    pub fn new(contact_id: u32) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_data: BaseModelData::new(), // ID will be auto-generated by OurDB
 | 
			
		||||
            contact_id,
 | 
			
		||||
            status: AttendanceStatus::NoResponse,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a new attendee with optional ID (use None for auto-generated ID)
 | 
			
		||||
    pub fn new_with_id(id: Option<u32>, contact_id: u32) -> Self {
 | 
			
		||||
        let mut base_data = BaseModelData::new();
 | 
			
		||||
        if let Some(id) = id {
 | 
			
		||||
            base_data.update_id(id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            base_data,
 | 
			
		||||
            contact_id,
 | 
			
		||||
            status: AttendanceStatus::NoResponse,
 | 
			
		||||
        }
 | 
			
		||||
@@ -39,10 +57,10 @@ impl Attendee {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents an event in a calendar
 | 
			
		||||
#[model]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
 | 
			
		||||
pub struct Event {
 | 
			
		||||
    /// Base model data
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub base_data: BaseModelData,
 | 
			
		||||
    /// Title of the event
 | 
			
		||||
    pub title: String,
 | 
			
		||||
@@ -52,17 +70,40 @@ pub struct Event {
 | 
			
		||||
    pub start_time: DateTime<Utc>,
 | 
			
		||||
    /// End time of the event
 | 
			
		||||
    pub end_time: DateTime<Utc>,
 | 
			
		||||
    /// List of attendees for the event
 | 
			
		||||
    pub attendees: Vec<Attendee>,
 | 
			
		||||
    /// List of attendee IDs for the event
 | 
			
		||||
    pub attendees: Vec<u32>,
 | 
			
		||||
    /// Optional location of the event
 | 
			
		||||
    pub location: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Event {
 | 
			
		||||
    /// Creates a new event
 | 
			
		||||
    /// Creates a new event with auto-generated ID
 | 
			
		||||
    pub fn new(title: impl ToString, start_time: DateTime<Utc>, end_time: DateTime<Utc>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_data: BaseModelData::new(),
 | 
			
		||||
            base_data: BaseModelData::new(), // ID will be auto-generated by OurDB
 | 
			
		||||
            title: title.to_string(),
 | 
			
		||||
            description: None,
 | 
			
		||||
            start_time,
 | 
			
		||||
            end_time,
 | 
			
		||||
            attendees: Vec::new(),
 | 
			
		||||
            location: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a new event with optional ID (use None for auto-generated ID)
 | 
			
		||||
    pub fn new_with_id(
 | 
			
		||||
        id: Option<u32>,
 | 
			
		||||
        title: impl ToString,
 | 
			
		||||
        start_time: DateTime<Utc>,
 | 
			
		||||
        end_time: DateTime<Utc>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let mut base_data = BaseModelData::new();
 | 
			
		||||
        if let Some(id) = id {
 | 
			
		||||
            base_data.update_id(id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            base_data,
 | 
			
		||||
            title: title.to_string(),
 | 
			
		||||
            description: None,
 | 
			
		||||
            start_time,
 | 
			
		||||
@@ -90,26 +131,18 @@ impl Event {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Adds an attendee to the event
 | 
			
		||||
    pub fn add_attendee(mut self, attendee: Attendee) -> Self {
 | 
			
		||||
        // Prevent duplicate attendees by contact_id
 | 
			
		||||
        if !self.attendees.iter().any(|a| a.contact_id == attendee.contact_id) {
 | 
			
		||||
            self.attendees.push(attendee);
 | 
			
		||||
    /// Adds an attendee ID to the event
 | 
			
		||||
    pub fn add_attendee(mut self, attendee_id: u32) -> Self {
 | 
			
		||||
        // Prevent duplicate attendees by ID
 | 
			
		||||
        if !self.attendees.iter().any(|&a_id| a_id == attendee_id) {
 | 
			
		||||
            self.attendees.push(attendee_id);
 | 
			
		||||
        }
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Removes an attendee from the event by user_id
 | 
			
		||||
    pub fn remove_attendee(mut self, contact_id: u32) -> Self {
 | 
			
		||||
        self.attendees.retain(|a| a.contact_id != contact_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Updates the status of an existing attendee
 | 
			
		||||
    pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self {
 | 
			
		||||
        if let Some(attendee) = self.attendees.iter_mut().find(|a| a.contact_id == contact_id) {
 | 
			
		||||
            attendee.status = status;
 | 
			
		||||
        }
 | 
			
		||||
    /// Removes an attendee from the event by attendee ID
 | 
			
		||||
    pub fn remove_attendee(mut self, attendee_id: u32) -> Self {
 | 
			
		||||
        self.attendees.retain(|&a_id| a_id != attendee_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -130,14 +163,11 @@ impl Event {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a calendar with events
 | 
			
		||||
#[rhai_model_export(
 | 
			
		||||
    db_type = "std::sync::Arc<crate::db::hero::OurDB>",
 | 
			
		||||
)]
 | 
			
		||||
#[rhai_model_export(db_type = "std::sync::Arc<crate::db::hero::OurDB>")]
 | 
			
		||||
#[model]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
 | 
			
		||||
pub struct Calendar {
 | 
			
		||||
    /// Base model data
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub base_data: BaseModelData,
 | 
			
		||||
 | 
			
		||||
    /// Name of the calendar
 | 
			
		||||
@@ -194,7 +224,8 @@ impl Calendar {
 | 
			
		||||
 | 
			
		||||
    /// Removes an event from the calendar by its ID
 | 
			
		||||
    pub fn remove_event(mut self, event_id_to_remove: i64) -> Self {
 | 
			
		||||
        self.events.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove);
 | 
			
		||||
        self.events
 | 
			
		||||
            .retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,16 @@
 | 
			
		||||
use rhai::{Engine, EvalAltResult, NativeCallContext, ImmutableString};
 | 
			
		||||
use rhai::{Engine, EvalAltResult, ImmutableString, NativeCallContext};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use heromodels_core::BaseModelData;
 | 
			
		||||
use super::calendar::{AttendanceStatus, Attendee, Calendar, Event};
 | 
			
		||||
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 adapter_macros::{adapt_rhai_i64_input_fn, adapt_rhai_i64_input_method};
 | 
			
		||||
use heromodels_core::BaseModelData;
 | 
			
		||||
 | 
			
		||||
// Helper function for get_all_calendars registration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn new_calendar_rhai(name: String) -> Result<Calendar, Box<EvalAltResult>> {
 | 
			
		||||
        Ok(Calendar::new(None, name))
 | 
			
		||||
    Ok(Calendar::new(None, name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn new_event_rhai(
 | 
			
		||||
@@ -20,71 +19,113 @@ fn new_event_rhai(
 | 
			
		||||
    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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
    
 | 
			
		||||
        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"),
 | 
			
		||||
    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(),
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        Ok(event.update_attendee_status(contact_id_u32, status))
 | 
			
		||||
 | 
			
		||||
    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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
        adapt_rhai_i64_input_method!(Event, add_attendee, u32),
 | 
			
		||||
    );
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "remove_attendee",
 | 
			
		||||
        adapt_rhai_i64_input_method!(Event, remove_attendee, u32),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    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) });
 | 
			
		||||
    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());
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 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()) }); 
 | 
			
		||||
    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())
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Register getter for Calendar.base_data
 | 
			
		||||
    engine.register_get("base_data", |c: &mut Calendar| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(c.base_data.clone()) });
 | 
			
		||||
    engine.register_get(
 | 
			
		||||
        "base_data",
 | 
			
		||||
        |c: &mut Calendar| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(c.base_data.clone()) },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Register getters for BaseModelData
 | 
			
		||||
    engine.register_get("id", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.id.into()) }); 
 | 
			
		||||
    engine.register_get(
 | 
			
		||||
        "id",
 | 
			
		||||
        |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.id.into()) },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
 | 
			
		||||
    // 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()) });
 | 
			
		||||
    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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user