mdvault_core/domain/behaviors/
meeting.rs1use std::path::PathBuf;
11use std::sync::Arc;
12
13use chrono::Local;
14
15use crate::types::TypeDefinition;
16
17use super::super::context::{CreationContext, FieldPrompt, PromptContext, PromptType};
18use super::super::traits::{
19 DomainError, DomainResult, NoteBehavior, NoteIdentity, NoteLifecycle, NotePrompts,
20};
21
22pub struct MeetingBehavior {
24 typedef: Option<Arc<TypeDefinition>>,
25}
26
27impl MeetingBehavior {
28 pub fn new(typedef: Option<Arc<TypeDefinition>>) -> Self {
30 Self { typedef }
31 }
32}
33
34impl NoteIdentity for MeetingBehavior {
35 fn generate_id(&self, ctx: &CreationContext) -> DomainResult<Option<String>> {
36 if let Some(ref id) = ctx.core_metadata.meeting_id {
39 return Ok(Some(id.clone()));
40 }
41 Ok(None)
42 }
43
44 fn output_path(&self, ctx: &CreationContext) -> DomainResult<PathBuf> {
45 if let Some(ref td) = self.typedef
47 && let Some(ref output) = td.output
48 {
49 return super::render_output_template(output, ctx);
50 }
51
52 let meeting_id =
54 ctx.core_metadata.meeting_id.as_ref().ok_or_else(|| {
55 DomainError::PathResolution("meeting-id not set".into())
56 })?;
57
58 Ok(ctx.config.vault_root.join(format!("Meetings/{}.md", meeting_id)))
59 }
60
61 fn core_fields(&self) -> Vec<&'static str> {
62 vec!["type", "title", "meeting-id", "date", "attendees"]
63 }
64}
65
66impl NoteLifecycle for MeetingBehavior {
67 fn before_create(&self, ctx: &mut CreationContext) -> DomainResult<()> {
68 let date = ctx
70 .get_var("date")
71 .map(|s| s.to_string())
72 .unwrap_or_else(|| Local::now().format("%Y-%m-%d").to_string());
73
74 let meeting_id = generate_meeting_id(&ctx.config.vault_root, &date)?;
76
77 ctx.core_metadata.meeting_id = Some(meeting_id.clone());
79 ctx.core_metadata.date = Some(date.clone());
80 ctx.set_var("meeting-id", &meeting_id);
81 ctx.set_var("date", &date);
82
83 Ok(())
84 }
85
86 fn after_create(&self, ctx: &CreationContext, _content: &str) -> DomainResult<()> {
87 if let Some(ref output_path) = ctx.output_path {
89 let meeting_id = ctx.core_metadata.meeting_id.as_deref().unwrap_or("");
90 if let Err(e) = super::super::services::DailyLogService::log_creation(
91 ctx.config,
92 "meeting",
93 &ctx.title,
94 meeting_id,
95 output_path,
96 ) {
97 tracing::warn!("Failed to log to daily note: {}", e);
99 }
100 }
101
102 Ok(())
103 }
104}
105
106impl NotePrompts for MeetingBehavior {
107 fn type_prompts(&self, ctx: &PromptContext) -> Vec<FieldPrompt> {
108 let mut prompts = vec![];
109
110 if !ctx.provided_vars.contains_key("date") && !ctx.batch_mode {
112 prompts.push(FieldPrompt {
113 field_name: "date".into(),
114 prompt_text: "Meeting date".into(),
115 prompt_type: PromptType::Text,
116 required: false,
117 default_value: Some(Local::now().format("%Y-%m-%d").to_string()),
118 });
119 }
120
121 if !ctx.provided_vars.contains_key("attendees") && !ctx.batch_mode {
123 prompts.push(FieldPrompt {
124 field_name: "attendees".into(),
125 prompt_text: "Who's attending?".into(),
126 prompt_type: PromptType::Text,
127 required: false,
128 default_value: None,
129 });
130 }
131
132 prompts
133 }
134}
135
136impl NoteBehavior for MeetingBehavior {
137 fn type_name(&self) -> &'static str {
138 "meeting"
139 }
140}
141
142use std::fs;
145
146fn generate_meeting_id(vault_root: &std::path::Path, date: &str) -> DomainResult<String> {
148 let meetings_dir = vault_root.join("Meetings");
149 let prefix = format!("MTG-{}-", date);
150
151 let mut max_num = 0u32;
152
153 if meetings_dir.exists() {
154 for entry in fs::read_dir(&meetings_dir).map_err(DomainError::Io)? {
155 let entry = entry.map_err(DomainError::Io)?;
156 let name = entry.file_name();
157 let name_str = name.to_string_lossy();
158
159 if let Some(stem) = name_str.strip_suffix(".md")
161 && stem.starts_with(&prefix)
162 && let Some(num_str) = stem.strip_prefix(&prefix)
163 && let Ok(num) = num_str.parse::<u32>()
164 {
165 max_num = max_num.max(num);
166 }
167 }
168 }
169
170 Ok(format!("{}{:03}", prefix, max_num + 1))
171}