Skip to main content

oxios_kernel/kernel_handle/
calendar_api.rs

1//! Calendar API — 15th KernelHandle domain Facade.
2//!
3//! Provides typed access to the calendar engine for the rest of the kernel.
4//! Wraps [`oxios_calendar::CalendarEngine`] behind a clean async API.
5//! Publishes [`KernelEvent`] variants for calendar mutations.
6
7use std::sync::Arc;
8
9use chrono::{DateTime, Utc};
10
11use oxios_calendar::{
12    AlarmEvent, CalendarEngine, CreateResult, Event, EventDraft, EventPatch, FreeBusySlot,
13    UpdateResult,
14};
15
16use crate::event_bus::{EventBus, KernelEvent};
17
18/// Calendar API facade — 15th typed API in [`KernelHandle`].
19///
20/// Delegates all operations to the underlying [`CalendarEngine`]
21/// and publishes [`KernelEvent`] variants for each mutation.
22/// Constructed during kernel assembly and stored in `KernelHandle.calendar`.
23pub struct CalendarApi {
24    /// The calendar engine.
25    pub engine: Arc<CalendarEngine>,
26    /// Optional event bus for publishing calendar events.
27    event_bus: Option<EventBus>,
28}
29
30impl CalendarApi {
31    /// Create a new CalendarApi wrapping the given engine.
32    pub fn new(engine: Arc<CalendarEngine>) -> Self {
33        Self {
34            engine,
35            event_bus: None,
36        }
37    }
38
39    /// Create a new CalendarApi with event bus support.
40    pub fn with_event_bus(engine: Arc<CalendarEngine>, event_bus: EventBus) -> Self {
41        Self {
42            engine,
43            event_bus: Some(event_bus),
44        }
45    }
46
47    /// Create a new event and publish a `CalendarEventCreated` event.
48    pub async fn create(&self, draft: EventDraft) -> anyhow::Result<CreateResult> {
49        let title = draft.title.clone();
50        let start = draft.start.to_rfc3339();
51        let end = draft.end.to_rfc3339();
52        let result = self.engine.create(draft).await?;
53
54        if let Some(bus) = &self.event_bus {
55            let _ = bus.publish(KernelEvent::CalendarEventCreated {
56                uid: result.uid.clone(),
57                title,
58                start,
59                end,
60            });
61        }
62
63        Ok(result)
64    }
65
66    /// Update an existing event and publish a `CalendarEventUpdated` event.
67    pub async fn update(&self, uid: &str, patch: EventPatch) -> anyhow::Result<UpdateResult> {
68        let result = self.engine.update(uid, patch).await?;
69
70        if let Some(bus) = &self.event_bus {
71            // Fetch the event to get the current title
72            let title = self
73                .engine
74                .get(uid)
75                .await
76                .map(|e| e.title.clone())
77                .unwrap_or_default();
78
79            let _ = bus.publish(KernelEvent::CalendarEventUpdated {
80                uid: result.uid.clone(),
81                title,
82            });
83        }
84
85        Ok(result)
86    }
87
88    /// Delete an event by UID and publish a `CalendarEventDeleted` event.
89    pub async fn delete(&self, uid: &str) -> anyhow::Result<()> {
90        // Fetch title before deletion for the event
91        let title = self
92            .engine
93            .get(uid)
94            .await
95            .map(|e| e.title.clone())
96            .unwrap_or_default();
97
98        self.engine.delete(uid).await?;
99
100        if let Some(bus) = &self.event_bus {
101            let _ = bus.publish(KernelEvent::CalendarEventDeleted {
102                uid: uid.to_string(),
103                title,
104            });
105        }
106
107        Ok(())
108    }
109
110    /// Get a single event by UID.
111    pub async fn get(&self, uid: &str) -> anyhow::Result<Event> {
112        self.engine.get(uid).await
113    }
114
115    /// List events in a time range `[from, to)`.
116    pub async fn list(&self, from: DateTime<Utc>, to: DateTime<Utc>) -> anyhow::Result<Vec<Event>> {
117        self.engine.list(from, to).await
118    }
119
120    /// Search events by text query.
121    pub async fn search(&self, query: &str) -> anyhow::Result<Vec<Event>> {
122        self.engine.search(query).await
123    }
124
125    /// Compute free/busy slots in a time range.
126    pub async fn freebusy(
127        &self,
128        from: DateTime<Utc>,
129        to: DateTime<Utc>,
130    ) -> anyhow::Result<Vec<FreeBusySlot>> {
131        self.engine.freebusy(from, to).await
132    }
133
134    /// Find pending alarms in a time range.
135    pub fn find_pending_alarms(&self, from: DateTime<Utc>, to: DateTime<Utc>) -> Vec<AlarmEvent> {
136        self.engine.find_pending_alarms(from, to)
137    }
138}