1use crate::{PaginatedResponse, Photo, Result, RideWithGpsClient, Visibility};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct Organizer {
9 pub id: Option<u64>,
11
12 pub name: Option<String>,
14
15 pub created_at: Option<String>,
17
18 pub updated_at: Option<String>,
20}
21
22#[derive(Debug, Clone, Deserialize, Serialize)]
24pub struct Event {
25 pub id: u64,
27
28 pub name: Option<String>,
30
31 pub description: Option<String>,
33
34 pub location: Option<String>,
36
37 pub lat: Option<f64>,
39
40 pub lng: Option<f64>,
42
43 pub visibility: Option<Visibility>,
45
46 pub url: Option<String>,
48
49 pub html_url: Option<String>,
51
52 pub time_zone: Option<String>,
54
55 pub start_date: Option<String>,
57
58 pub start_time: Option<String>,
60
61 pub end_date: Option<String>,
63
64 pub end_time: Option<String>,
66
67 pub all_day: Option<bool>,
69
70 pub starts_at: Option<String>,
72
73 pub ends_at: Option<String>,
75
76 pub registration_opens_at: Option<String>,
78
79 pub registration_closes_at: Option<String>,
81
82 pub user_id: Option<u64>,
84
85 pub created_at: Option<String>,
87
88 pub updated_at: Option<String>,
90
91 pub slug: Option<String>,
93
94 pub logo_url: Option<String>,
96
97 pub banner_url: Option<String>,
99
100 pub registration_required: Option<bool>,
102
103 pub max_attendees: Option<u32>,
105
106 pub attendee_count: Option<u32>,
108
109 pub organizers: Option<Vec<Organizer>>,
111
112 pub photos: Option<Vec<Photo>>,
114}
115
116#[derive(Debug, Clone, Default, Serialize)]
118pub struct ListEventsParams {
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub name: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub visibility: Option<Visibility>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub page: Option<u32>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub page_size: Option<u32>,
134}
135
136#[derive(Debug, Clone, Serialize)]
138pub struct EventRequest {
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub name: Option<String>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub description: Option<String>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub location: Option<String>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub visibility: Option<Visibility>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub starts_at: Option<String>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub ends_at: Option<String>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub registration_opens_at: Option<String>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub registration_closes_at: Option<String>,
170
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub registration_required: Option<bool>,
174
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub max_attendees: Option<u32>,
178}
179
180impl RideWithGpsClient {
181 pub fn list_events(
202 &self,
203 params: Option<&ListEventsParams>,
204 ) -> Result<PaginatedResponse<Event>> {
205 let mut url = "/api/v1/events.json".to_string();
206
207 if let Some(params) = params {
208 let query = serde_json::to_value(params)?;
209 if let Some(obj) = query.as_object() {
210 if !obj.is_empty() {
211 let query_str = serde_urlencoded::to_string(obj).map_err(|e| {
212 crate::Error::ApiError(format!("Failed to encode query: {}", e))
213 })?;
214 url.push('?');
215 url.push_str(&query_str);
216 }
217 }
218 }
219
220 self.get(&url)
221 }
222
223 pub fn create_event(&self, event: &EventRequest) -> Result<Event> {
257 #[derive(Deserialize)]
258 struct EventWrapper {
259 event: Event,
260 }
261
262 let wrapper: EventWrapper = self.post("/api/v1/events.json", event)?;
263 Ok(wrapper.event)
264 }
265
266 pub fn get_event(&self, id: u64) -> Result<Event> {
287 #[derive(Deserialize)]
288 struct EventWrapper {
289 event: Event,
290 }
291
292 let wrapper: EventWrapper = self.get(&format!("/api/v1/events/{}.json", id))?;
293 Ok(wrapper.event)
294 }
295
296 pub fn update_event(&self, id: u64, event: &EventRequest) -> Result<Event> {
331 #[derive(Deserialize)]
332 struct EventWrapper {
333 event: Event,
334 }
335
336 let wrapper: EventWrapper = self.put(&format!("/api/v1/events/{}.json", id), event)?;
337 Ok(wrapper.event)
338 }
339
340 pub fn delete_event(&self, id: u64) -> Result<()> {
360 self.delete(&format!("/api/v1/events/{}.json", id))
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn test_event_deserialization() {
370 let json = r#"{
371 "id": 789,
372 "name": "Test Event",
373 "location": "Portland, OR",
374 "visibility": "public",
375 "starts_at": "2025-06-01T09:00:00",
376 "attendee_count": 25
377 }"#;
378
379 let event: Event = serde_json::from_str(json).unwrap();
380 assert_eq!(event.id, 789);
381 assert_eq!(event.name.as_deref(), Some("Test Event"));
382 assert_eq!(event.location.as_deref(), Some("Portland, OR"));
383 assert_eq!(event.visibility, Some(Visibility::Public));
384 assert_eq!(event.attendee_count, Some(25));
385 }
386
387 #[test]
388 fn test_event_request_serialization() {
389 let req = EventRequest {
390 name: Some("My Event".to_string()),
391 description: Some("Fun ride".to_string()),
392 location: None,
393 visibility: Some(Visibility::Public),
394 starts_at: None,
395 ends_at: None,
396 registration_opens_at: None,
397 registration_closes_at: None,
398 registration_required: Some(true),
399 max_attendees: Some(100),
400 };
401
402 let json = serde_json::to_value(&req).unwrap();
403 assert_eq!(json.get("name").unwrap(), "My Event");
404 assert_eq!(json.get("visibility").unwrap(), "public");
405 assert_eq!(json.get("registration_required").unwrap(), true);
406 assert_eq!(json.get("max_attendees").unwrap(), 100);
407 }
408
409 #[test]
410 fn test_event_wrapper_deserialization() {
411 let json = r#"{
412 "event": {
413 "id": 999,
414 "name": "Wrapped Event",
415 "location": "Portland, OR",
416 "visibility": "public"
417 }
418 }"#;
419
420 #[derive(Deserialize)]
421 struct EventWrapper {
422 event: Event,
423 }
424
425 let wrapper: EventWrapper = serde_json::from_str(json).unwrap();
426 assert_eq!(wrapper.event.id, 999);
427 assert_eq!(wrapper.event.name.as_deref(), Some("Wrapped Event"));
428 assert_eq!(wrapper.event.location.as_deref(), Some("Portland, OR"));
429 }
430
431 #[test]
432 fn test_event_with_dates_and_times() {
433 let json = r#"{
434 "id": 555,
435 "name": "Time Test Event",
436 "start_date": "2025-06-01",
437 "start_time": "09:00",
438 "end_date": "2025-06-01",
439 "end_time": "17:00",
440 "all_day": false,
441 "time_zone": "America/Los_Angeles",
442 "starts_at": "2025-06-01T09:00:00-07:00",
443 "ends_at": "2025-06-01T17:00:00-07:00"
444 }"#;
445
446 let event: Event = serde_json::from_str(json).unwrap();
447 assert_eq!(event.id, 555);
448 assert_eq!(event.start_date.as_deref(), Some("2025-06-01"));
449 assert_eq!(event.start_time.as_deref(), Some("09:00"));
450 assert_eq!(event.all_day, Some(false));
451 assert_eq!(event.time_zone.as_deref(), Some("America/Los_Angeles"));
452 }
453
454 #[test]
455 fn test_organizer_deserialization() {
456 let json = r#"{
457 "id": 42,
458 "name": "Bike Club",
459 "created_at": "2020-01-01T00:00:00Z"
460 }"#;
461
462 let organizer: Organizer = serde_json::from_str(json).unwrap();
463 assert_eq!(organizer.id, Some(42));
464 assert_eq!(organizer.name.as_deref(), Some("Bike Club"));
465 }
466}