1use std::sync::Arc;
15
16use myko::command::{CommandContext, CommandError, CommandHandler};
17use myko::prelude::*;
18use myko::saga::{SagaContext, SagaHandler};
19use myko::wire::MEventType;
20
21use crate::entities::{
22 Applied, GetAppliedsByIds, GetLogEntrysByQuery, LogEntry, PartialLogEntry, applied_id,
23};
24
25#[myko_command]
30pub struct ApplyLogEntry {
31 pub cbor_b64: String,
32 pub event_oid: String,
34}
35
36impl CommandHandler for ApplyLogEntry {
37 fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
38 use base64::Engine as _;
39 let err = |m: String| CommandError {
40 tx: ctx.tx().to_string(),
41 command_id: ctx.command_id.to_string(),
42 message: m,
43 };
44 let bytes = base64::engine::general_purpose::STANDARD
45 .decode(&self.cbor_b64)
46 .map_err(|e| err(format!("invalid base64 in LogEntry: {e}")))?;
47 let event: myko::wire::MEvent = ciborium::from_reader(bytes.as_slice())
48 .map_err(|e| err(format!("invalid CBOR in LogEntry: {e}")))?;
49 if event.item_type == LogEntry::ENTITY_NAME_STATIC {
51 return Err(err("refusing to unwrap a nested LogEntry".into()));
52 }
53 let Some(item_id) = event.item.get("id").and_then(|v| v.as_str()) else {
54 return Err(err("wrapped event has no item id".into()));
55 };
56
57 let guard_id = applied_id(&event.item_type, item_id);
59 let existing = ctx.exec_query_first(GetAppliedsByIds {
60 ids: vec![guard_id.clone().into()],
61 })?;
62 let incoming = (event.created_at.clone(), self.event_oid.clone());
63 if let Some(applied) = existing
64 && (applied.event_created.as_str(), applied.event_oid.as_str())
65 > (incoming.0.as_str(), incoming.1.as_str())
66 {
67 return Ok(()); }
69
70 ctx.emit_event_batch(vec![event])?;
71 ctx.emit_set(&Applied {
72 id: guard_id.into(),
73 event_created: incoming.0,
74 event_oid: incoming.1,
75 })?;
76 Ok(())
77 }
78}
79
80myko::register_command_handler!(ApplyLogEntry);
81
82#[myko_saga]
85pub struct UnwrapLogEntries;
86
87impl SagaHandler for UnwrapLogEntries {
88 type EventItem = LogEntry;
89 type Command = ApplyLogEntry;
90 const EVENT_TYPE: MEventType = MEventType::SET;
91
92 fn handle(
93 item: LogEntry,
94 _event: myko::wire::MEvent,
95 _ctx: Arc<SagaContext>,
96 ) -> Option<ApplyLogEntry> {
97 Some(ApplyLogEntry {
98 cbor_b64: item.cbor_b64,
99 event_oid: item.id.to_string(),
100 })
101 }
102}
103
104#[myko_report_output]
108pub struct LogEntryBucketsOut {
109 pub buckets: std::collections::BTreeMap<String, String>,
110}
111
112#[myko_report(LogEntryBucketsOut)]
113pub struct LogEntryBuckets {
114 pub project_id: String,
115}
116
117impl myko::report::ReportHandler for LogEntryBuckets {
118 type Output = LogEntryBucketsOut;
119
120 #[cfg(not(target_arch = "wasm32"))]
121 fn compute(
122 &self,
123 ctx: myko::report::ReportContext,
124 ) -> impl myko::hyphae::MaterializeDefinite<Arc<Self::Output>> {
125 ctx.query_map(GetLogEntrysByQuery(PartialLogEntry {
126 project_id: Some(self.project_id.clone()),
127 ..Default::default()
128 }))
129 .items()
130 .map(|entries| {
131 Arc::new(LogEntryBucketsOut {
132 buckets: crate::merkle::bucket_hashes(entries.iter().map(|e| &*e.id.0)),
133 })
134 })
135 }
136}
137
138#[myko_report_output]
140pub struct LogEntryBucketIdsOut {
141 pub ids: Vec<String>,
142}
143
144#[myko_report(LogEntryBucketIdsOut)]
145pub struct LogEntryBucketIds {
146 pub project_id: String,
147 pub bucket: String,
148}
149
150impl myko::report::ReportHandler for LogEntryBucketIds {
151 type Output = LogEntryBucketIdsOut;
152
153 #[cfg(not(target_arch = "wasm32"))]
154 fn compute(
155 &self,
156 ctx: myko::report::ReportContext,
157 ) -> impl myko::hyphae::MaterializeDefinite<Arc<Self::Output>> {
158 let bucket = self.bucket.clone();
159 ctx.query_map(GetLogEntrysByQuery(PartialLogEntry {
160 project_id: Some(self.project_id.clone()),
161 ..Default::default()
162 }))
163 .items()
164 .map(move |entries| {
165 let mut ids: Vec<String> = entries
166 .iter()
167 .map(|e| e.id.to_string())
168 .filter(|id| id.starts_with(&bucket))
169 .collect();
170 ids.sort_unstable();
171 Arc::new(LogEntryBucketIdsOut { ids })
172 })
173 }
174}