1use crate::auth::TokenManager;
2use crate::config::Config;
3use chrono::{DateTime, Utc};
4use log::{debug, error};
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use tokio::sync::Mutex;
9use uuid::Uuid;
10
11const CALENDAR_API_BASE_URL: &str = "https://www.googleapis.com/calendar/v3";
12
13use crate::errors::{CalendarApiError, CalendarResult};
14
15type Result<T> = CalendarResult<T>;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct CalendarEvent {
21 pub id: Option<String>,
22 pub summary: String,
23 pub description: Option<String>,
24 pub location: Option<String>,
25 pub start_time: DateTime<Utc>,
26 pub end_time: DateTime<Utc>,
27 pub attendees: Vec<Attendee>,
28 pub conference_data: Option<ConferenceData>,
29 pub html_link: Option<String>,
30 pub creator: Option<EventOrganizer>,
31 pub organizer: Option<EventOrganizer>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct EventOrganizer {
36 pub email: String,
37 pub display_name: Option<String>,
38 pub self_: Option<bool>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Attendee {
43 pub email: String,
44 pub display_name: Option<String>,
45 pub response_status: Option<String>,
46 pub optional: Option<bool>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ConferenceData {
51 pub conference_solution: Option<ConferenceSolution>,
52 pub entry_points: Vec<EntryPoint>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ConferenceSolution {
57 pub name: String,
58 pub key: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct EntryPoint {
63 pub entry_point_type: String,
64 pub uri: String,
65 pub label: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct CalendarList {
70 pub calendars: Vec<CalendarInfo>,
71 pub next_page_token: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CalendarInfo {
76 pub id: String,
77 pub summary: String,
78 pub description: Option<String>,
79 pub primary: Option<bool>,
80}
81
82#[derive(Debug, Clone)]
84pub struct CalendarClient {
85 client: Client,
86 token_manager: Arc<Mutex<TokenManager>>,
87}
88
89impl CalendarClient {
90 pub fn new(config: &Config) -> Self {
91 let client = Client::new();
92 let token_manager = Arc::new(Mutex::new(TokenManager::new(config)));
94
95 Self {
96 client,
97 token_manager,
98 }
99 }
100
101 pub async fn list_calendars(&self) -> Result<CalendarList> {
103 let token = self
104 .token_manager
105 .lock()
106 .await
107 .get_token(&self.client)
108 .await
109 .map_err(|e| CalendarApiError::AuthError(e.to_string()))?;
110
111 let url = format!("{}/users/me/calendarList", CALENDAR_API_BASE_URL);
112 debug!("Listing calendars from: {}", url);
113
114 let response = self
115 .client
116 .get(&url)
117 .header("Authorization", format!("Bearer {}", token))
118 .send()
119 .await
120 .map_err(|e| CalendarApiError::NetworkError(e.to_string()))?;
121
122 let status = response.status();
123 if !status.is_success() {
124 let error_text = response
125 .text()
126 .await
127 .unwrap_or_else(|_| "<no response body>".to_string());
128 return Err(CalendarApiError::ApiError(format!(
129 "Failed to list calendars. Status: {}, Error: {}",
130 status, error_text
131 )));
132 }
133
134 let json_response = response
135 .json::<serde_json::Value>()
136 .await
137 .map_err(|e| CalendarApiError::ParseError(e.to_string()))?;
138
139 let mut calendars = Vec::new();
140
141 if let Some(items) = json_response.get("items").and_then(|v| v.as_array()) {
142 for item in items {
143 let id = item
144 .get("id")
145 .and_then(|v| v.as_str())
146 .ok_or_else(|| CalendarApiError::ParseError("Missing calendar id".to_string()))?
147 .to_string();
148
149 let summary = item
150 .get("summary")
151 .and_then(|v| v.as_str())
152 .unwrap_or("Unknown Calendar")
153 .to_string();
154
155 let description = item
156 .get("description")
157 .and_then(|v| v.as_str())
158 .map(|s| s.to_string());
159
160 let primary = item.get("primary").and_then(|v| v.as_bool());
161
162 calendars.push(CalendarInfo {
163 id,
164 summary,
165 description,
166 primary,
167 });
168 }
169 }
170
171 let next_page_token = json_response
172 .get("nextPageToken")
173 .and_then(|v| v.as_str())
174 .map(|s| s.to_string());
175
176 Ok(CalendarList {
177 calendars,
178 next_page_token,
179 })
180 }
181
182 pub async fn list_events(
184 &self,
185 calendar_id: &str,
186 max_results: Option<u32>,
187 time_min: Option<DateTime<Utc>>,
188 time_max: Option<DateTime<Utc>>,
189 ) -> Result<Vec<CalendarEvent>> {
190 let token = self
191 .token_manager
192 .lock()
193 .await
194 .get_token(&self.client)
195 .await
196 .map_err(|e| CalendarApiError::AuthError(e.to_string()))?;
197
198 let mut url = format!("{}/calendars/{}/events", CALENDAR_API_BASE_URL, calendar_id);
199
200 let mut query_parts = Vec::new();
202
203 if let Some(max) = max_results {
204 query_parts.push(format!("maxResults={}", max));
205 }
206
207 if let Some(min_time) = time_min {
208 let encoded_time = urlencoding::encode(&min_time.to_rfc3339()).into_owned();
209 query_parts.push(format!("timeMin={}", encoded_time));
210 }
211
212 if let Some(max_time) = time_max {
213 let encoded_time = urlencoding::encode(&max_time.to_rfc3339()).into_owned();
214 query_parts.push(format!("timeMax={}", encoded_time));
215 }
216
217 query_parts.push("singleEvents=true".to_string());
219
220 query_parts.push("orderBy=startTime".to_string());
222
223 if !query_parts.is_empty() {
224 url = format!("{}?{}", url, query_parts.join("&"));
225 }
226
227 debug!("Listing events from: {}", url);
228
229 let response = self
230 .client
231 .get(&url)
232 .header("Authorization", format!("Bearer {}", token))
233 .send()
234 .await
235 .map_err(|e| CalendarApiError::NetworkError(e.to_string()))?;
236
237 let status = response.status();
238 if !status.is_success() {
239 let error_text = response
240 .text()
241 .await
242 .unwrap_or_else(|_| "<no response body>".to_string());
243 return Err(CalendarApiError::ApiError(format!(
244 "Failed to list events. Status: {}, Error: {}",
245 status, error_text
246 )));
247 }
248
249 let json_response = response
250 .json::<serde_json::Value>()
251 .await
252 .map_err(|e| CalendarApiError::ParseError(e.to_string()))?;
253
254 let mut events = Vec::new();
255
256 if let Some(items) = json_response.get("items").and_then(|v| v.as_array()) {
257 for item in items {
258 if let Ok(event) = self.parse_event(item) {
259 events.push(event);
260 } else {
261 error!("Failed to parse event: {:?}", item);
263 }
264 }
265 }
266
267 Ok(events)
268 }
269
270 pub async fn create_event(
272 &self,
273 calendar_id: &str,
274 event: CalendarEvent,
275 ) -> Result<CalendarEvent> {
276 let token = self
277 .token_manager
278 .lock()
279 .await
280 .get_token(&self.client)
281 .await
282 .map_err(|e| CalendarApiError::AuthError(e.to_string()))?;
283
284 let url = format!("{}/calendars/{}/events", CALENDAR_API_BASE_URL, calendar_id);
285 debug!("Creating new event in calendar {}", calendar_id);
286
287 let mut event_data = serde_json::Map::new();
289 event_data.insert(
290 "summary".to_string(),
291 serde_json::Value::String(event.summary),
292 );
293
294 if let Some(desc) = event.description {
295 event_data.insert("description".to_string(), serde_json::Value::String(desc));
296 }
297
298 if let Some(loc) = event.location {
299 event_data.insert("location".to_string(), serde_json::Value::String(loc));
300 }
301
302 let mut start = serde_json::Map::new();
304 start.insert(
305 "dateTime".to_string(),
306 serde_json::Value::String(event.start_time.to_rfc3339()),
307 );
308 start.insert(
309 "timeZone".to_string(),
310 serde_json::Value::String("UTC".to_string()),
311 );
312 event_data.insert("start".to_string(), serde_json::Value::Object(start));
313
314 let mut end = serde_json::Map::new();
316 end.insert(
317 "dateTime".to_string(),
318 serde_json::Value::String(event.end_time.to_rfc3339()),
319 );
320 end.insert(
321 "timeZone".to_string(),
322 serde_json::Value::String("UTC".to_string()),
323 );
324 event_data.insert("end".to_string(), serde_json::Value::Object(end));
325
326 if !event.attendees.is_empty() {
328 let attendees = event
329 .attendees
330 .iter()
331 .map(|a| {
332 let mut attendee = serde_json::Map::new();
333 attendee.insert(
334 "email".to_string(),
335 serde_json::Value::String(a.email.clone()),
336 );
337
338 if let Some(name) = &a.display_name {
339 attendee.insert(
340 "displayName".to_string(),
341 serde_json::Value::String(name.clone()),
342 );
343 }
344
345 if let Some(status) = &a.response_status {
346 attendee.insert(
347 "responseStatus".to_string(),
348 serde_json::Value::String(status.clone()),
349 );
350 }
351
352 if let Some(optional) = a.optional {
353 attendee.insert("optional".to_string(), serde_json::Value::Bool(optional));
354 }
355
356 serde_json::Value::Object(attendee)
357 })
358 .collect::<Vec<_>>();
359
360 event_data.insert("attendees".to_string(), serde_json::Value::Array(attendees));
361 }
362
363 let request_id = Uuid::new_v4().to_string();
367 debug!("Using idempotency header X-Goog-Request-ID: {}", request_id);
368
369 let response = self
373 .client
374 .post(&url)
375 .header("Authorization", format!("Bearer {}", token))
376 .header("Content-Type", "application/json")
377 .header("X-Goog-Request-ID", request_id)
379 .json(&event_data)
380 .send()
381 .await
382 .map_err(|e| CalendarApiError::NetworkError(e.to_string()))?;
383
384 let status = response.status();
385 if !status.is_success() {
386 let error_text = response
387 .text()
388 .await
389 .unwrap_or_else(|_| "<no response body>".to_string());
390 return Err(CalendarApiError::ApiError(format!(
391 "Failed to create event. Status: {}, Error: {}",
392 status, error_text
393 )));
394 }
395
396 let json_response = response
397 .json::<serde_json::Value>()
398 .await
399 .map_err(|e| CalendarApiError::ParseError(e.to_string()))?;
400
401 self.parse_event(&json_response)
402 }
403
404 pub async fn get_event(&self, calendar_id: &str, event_id: &str) -> Result<CalendarEvent> {
406 let token = self
407 .token_manager
408 .lock()
409 .await
410 .get_token(&self.client)
411 .await
412 .map_err(|e| CalendarApiError::AuthError(e.to_string()))?;
413
414 let url = format!(
415 "{}/calendars/{}/events/{}",
416 CALENDAR_API_BASE_URL, calendar_id, event_id
417 );
418 debug!("Getting event {} from calendar {}", event_id, calendar_id);
419
420 let response = self
421 .client
422 .get(&url)
423 .header("Authorization", format!("Bearer {}", token))
424 .send()
425 .await
426 .map_err(|e| CalendarApiError::NetworkError(e.to_string()))?;
427
428 let status = response.status();
429 if !status.is_success() {
430 let error_text = response
431 .text()
432 .await
433 .unwrap_or_else(|_| "<no response body>".to_string());
434 return Err(CalendarApiError::ApiError(format!(
435 "Failed to get event. Status: {}, Error: {}",
436 status, error_text
437 )));
438 }
439
440 let json_response = response
441 .json::<serde_json::Value>()
442 .await
443 .map_err(|e| CalendarApiError::ParseError(e.to_string()))?;
444
445 self.parse_event(&json_response)
446 }
447
448 fn parse_event(&self, item: &serde_json::Value) -> Result<CalendarEvent> {
450 let id = item
451 .get("id")
452 .and_then(|v| v.as_str())
453 .map(|s| s.to_string());
454
455 let summary = item
456 .get("summary")
457 .and_then(|v| v.as_str())
458 .ok_or_else(|| CalendarApiError::ParseError("Missing event summary".to_string()))?
459 .to_string();
460
461 let description = item
462 .get("description")
463 .and_then(|v| v.as_str())
464 .map(|s| s.to_string());
465
466 let location = item
467 .get("location")
468 .and_then(|v| v.as_str())
469 .map(|s| s.to_string());
470
471 let start_time = item
473 .get("start")
474 .and_then(|v| v.get("dateTime"))
475 .and_then(|v| v.as_str())
476 .ok_or_else(|| CalendarApiError::ParseError("Missing start time".to_string()))?;
477
478 let end_time = item
479 .get("end")
480 .and_then(|v| v.get("dateTime"))
481 .and_then(|v| v.as_str())
482 .ok_or_else(|| CalendarApiError::ParseError("Missing end time".to_string()))?;
483
484 let start_dt = DateTime::parse_from_rfc3339(start_time)
486 .map_err(|e| CalendarApiError::ParseError(format!("Invalid start time: {}", e)))?
487 .with_timezone(&Utc);
488
489 let end_dt = DateTime::parse_from_rfc3339(end_time)
490 .map_err(|e| CalendarApiError::ParseError(format!("Invalid end time: {}", e)))?
491 .with_timezone(&Utc);
492
493 let mut attendees = Vec::new();
495 if let Some(attendee_list) = item.get("attendees").and_then(|v| v.as_array()) {
496 for attendee in attendee_list {
497 let email = attendee
498 .get("email")
499 .and_then(|v| v.as_str())
500 .ok_or_else(|| {
501 CalendarApiError::ParseError("Missing attendee email".to_string())
502 })?
503 .to_string();
504
505 let display_name = attendee
506 .get("displayName")
507 .and_then(|v| v.as_str())
508 .map(|s| s.to_string());
509
510 let response_status = attendee
511 .get("responseStatus")
512 .and_then(|v| v.as_str())
513 .map(|s| s.to_string());
514
515 let optional = attendee.get("optional").and_then(|v| v.as_bool());
516
517 attendees.push(Attendee {
518 email,
519 display_name,
520 response_status,
521 optional,
522 });
523 }
524 }
525
526 let conference_data = if let Some(conf_data) = item.get("conferenceData") {
528 let mut entry_points = Vec::new();
529
530 if let Some(entry_point_list) = conf_data.get("entryPoints").and_then(|v| v.as_array())
531 {
532 for entry_point in entry_point_list {
533 if let (Some(entry_type), Some(uri)) = (
534 entry_point.get("entryPointType").and_then(|v| v.as_str()),
535 entry_point.get("uri").and_then(|v| v.as_str()),
536 ) {
537 entry_points.push(EntryPoint {
538 entry_point_type: entry_type.to_string(),
539 uri: uri.to_string(),
540 label: entry_point
541 .get("label")
542 .and_then(|v| v.as_str())
543 .map(|s| s.to_string()),
544 });
545 }
546 }
547 }
548
549 let conference_solution = conf_data.get("conferenceSolution").and_then(|sol| {
550 sol.get("name")
551 .and_then(|v| v.as_str())
552 .map(|name| ConferenceSolution {
553 name: name.to_string(),
554 key: sol
555 .get("key")
556 .and_then(|v| v.as_str())
557 .map(|s| s.to_string()),
558 })
559 });
560
561 if !entry_points.is_empty() || conference_solution.is_some() {
562 Some(ConferenceData {
563 conference_solution,
564 entry_points,
565 })
566 } else {
567 None
568 }
569 } else {
570 None
571 };
572
573 let html_link = item
575 .get("htmlLink")
576 .and_then(|v| v.as_str())
577 .map(|s| s.to_string());
578
579 let creator = item.get("creator").and_then(|c| {
581 c.get("email")
582 .and_then(|v| v.as_str())
583 .map(|email| EventOrganizer {
584 email: email.to_string(),
585 display_name: c
586 .get("displayName")
587 .and_then(|v| v.as_str())
588 .map(|s| s.to_string()),
589 self_: c.get("self").and_then(|v| v.as_bool()),
590 })
591 });
592
593 let organizer = item.get("organizer").and_then(|o| {
595 o.get("email")
596 .and_then(|v| v.as_str())
597 .map(|email| EventOrganizer {
598 email: email.to_string(),
599 display_name: o
600 .get("displayName")
601 .and_then(|v| v.as_str())
602 .map(|s| s.to_string()),
603 self_: o.get("self").and_then(|v| v.as_bool()),
604 })
605 });
606
607 Ok(CalendarEvent {
608 id,
609 summary,
610 description,
611 location,
612 start_time: start_dt,
613 end_time: end_dt,
614 attendees,
615 conference_data,
616 html_link,
617 creator,
618 organizer,
619 })
620 }
621}