ridewithgps_client/
members.rs

1//! Club member related types and methods
2//!
3//! Note: These endpoints are only available to organization accounts.
4
5use crate::{PaginatedResponse, Result, RideWithGpsClient};
6use serde::{Deserialize, Serialize};
7
8/// A club member
9#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct Member {
11    /// Member ID
12    pub id: u64,
13
14    /// User ID
15    pub user_id: Option<u64>,
16
17    /// Organization ID
18    pub organization_id: Option<u64>,
19
20    /// API URL
21    pub url: Option<String>,
22
23    /// Whether the member is active
24    pub active: Option<bool>,
25
26    /// Whether the member is an admin
27    pub admin: Option<bool>,
28
29    /// Whether the member can manage routes
30    pub manages_routes: Option<bool>,
31
32    /// Whether the member can manage members
33    pub manages_members: Option<bool>,
34
35    /// Whether the member can manage billing
36    pub manages_billing: Option<bool>,
37
38    /// When the member was approved
39    pub approved_at: Option<String>,
40
41    /// Member role
42    pub role: Option<String>,
43
44    /// Member status
45    pub status: Option<String>,
46
47    /// User name
48    pub name: Option<String>,
49
50    /// User email
51    pub email: Option<String>,
52
53    /// Joined timestamp
54    pub joined_at: Option<String>,
55
56    /// Created timestamp
57    pub created_at: Option<String>,
58
59    /// Updated timestamp
60    pub updated_at: Option<String>,
61
62    /// Permissions
63    pub permissions: Option<MemberPermissions>,
64
65    /// User object
66    pub user: Option<crate::User>,
67}
68
69/// Member permissions
70#[derive(Debug, Clone, Deserialize, Serialize)]
71pub struct MemberPermissions {
72    /// Can manage routes
73    pub manage_routes: Option<bool>,
74
75    /// Can manage events
76    pub manage_events: Option<bool>,
77
78    /// Can manage members
79    pub manage_members: Option<bool>,
80
81    /// Can view analytics
82    pub view_analytics: Option<bool>,
83}
84
85/// Parameters for listing members
86#[derive(Debug, Clone, Default, Serialize)]
87pub struct ListMembersParams {
88    /// Filter by member name
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub name: Option<String>,
91
92    /// Filter by member role
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub role: Option<String>,
95
96    /// Filter by member status
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub status: Option<String>,
99
100    /// Page number
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub page: Option<u32>,
103
104    /// Page size
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub page_size: Option<u32>,
107}
108
109/// Request to update member permissions/status
110#[derive(Debug, Clone, Serialize)]
111pub struct UpdateMemberRequest {
112    /// Member role
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub role: Option<String>,
115
116    /// Member status
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub status: Option<String>,
119
120    /// Permissions
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub permissions: Option<MemberPermissions>,
123}
124
125impl RideWithGpsClient {
126    /// List club members
127    ///
128    /// Note: This endpoint is only available to organization accounts.
129    ///
130    /// # Arguments
131    ///
132    /// * `params` - Optional parameters for filtering and pagination
133    ///
134    /// # Example
135    ///
136    /// ```rust,no_run
137    /// use ridewithgps_client::RideWithGpsClient;
138    ///
139    /// let client = RideWithGpsClient::new(
140    ///     "https://ridewithgps.com",
141    ///     "your-api-key",
142    ///     Some("your-auth-token")
143    /// );
144    ///
145    /// let members = client.list_members(None).unwrap();
146    /// println!("Found {} members", members.results.len());
147    /// ```
148    pub fn list_members(
149        &self,
150        params: Option<&ListMembersParams>,
151    ) -> Result<PaginatedResponse<Member>> {
152        let mut url = "/api/v1/members.json".to_string();
153
154        if let Some(params) = params {
155            let query = serde_json::to_value(params)?;
156            if let Some(obj) = query.as_object() {
157                if !obj.is_empty() {
158                    let query_str = serde_urlencoded::to_string(obj).map_err(|e| {
159                        crate::Error::ApiError(format!("Failed to encode query: {}", e))
160                    })?;
161                    url.push('?');
162                    url.push_str(&query_str);
163                }
164            }
165        }
166
167        self.get(&url)
168    }
169
170    /// Get a specific member by ID
171    ///
172    /// Note: This endpoint is only available to organization accounts.
173    ///
174    /// # Arguments
175    ///
176    /// * `id` - The member ID
177    ///
178    /// # Example
179    ///
180    /// ```rust,no_run
181    /// use ridewithgps_client::RideWithGpsClient;
182    ///
183    /// let client = RideWithGpsClient::new(
184    ///     "https://ridewithgps.com",
185    ///     "your-api-key",
186    ///     Some("your-auth-token")
187    /// );
188    ///
189    /// let member = client.get_member(12345).unwrap();
190    /// println!("Member: {:?}", member);
191    /// ```
192    pub fn get_member(&self, id: u64) -> Result<Member> {
193        #[derive(Deserialize)]
194        struct MemberWrapper {
195            member: Member,
196        }
197
198        let wrapper: MemberWrapper = self.get(&format!("/api/v1/members/{}.json", id))?;
199        Ok(wrapper.member)
200    }
201
202    /// Update a member's permissions or status
203    ///
204    /// Note: This endpoint is only available to organization accounts.
205    ///
206    /// # Arguments
207    ///
208    /// * `id` - The member ID
209    /// * `member` - The updated member data
210    ///
211    /// # Example
212    ///
213    /// ```rust,no_run
214    /// use ridewithgps_client::{RideWithGpsClient, UpdateMemberRequest, MemberPermissions};
215    ///
216    /// let client = RideWithGpsClient::new(
217    ///     "https://ridewithgps.com",
218    ///     "your-api-key",
219    ///     Some("your-auth-token")
220    /// );
221    ///
222    /// let member_req = UpdateMemberRequest {
223    ///     role: Some("admin".to_string()),
224    ///     status: Some("active".to_string()),
225    ///     permissions: Some(MemberPermissions {
226    ///         manage_routes: Some(true),
227    ///         manage_events: Some(true),
228    ///         manage_members: Some(true),
229    ///         view_analytics: Some(true),
230    ///     }),
231    /// };
232    ///
233    /// let member = client.update_member(12345, &member_req).unwrap();
234    /// println!("Updated member: {:?}", member);
235    /// ```
236    pub fn update_member(&self, id: u64, member: &UpdateMemberRequest) -> Result<Member> {
237        #[derive(Deserialize)]
238        struct MemberWrapper {
239            member: Member,
240        }
241
242        let wrapper: MemberWrapper = self.put(&format!("/api/v1/members/{}.json", id), member)?;
243        Ok(wrapper.member)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_member_deserialization() {
253        let json = r#"{
254            "id": 123,
255            "user_id": 456,
256            "name": "John Doe",
257            "email": "john@example.com",
258            "role": "admin",
259            "status": "active",
260            "permissions": {
261                "manage_routes": true,
262                "manage_events": true,
263                "manage_members": false,
264                "view_analytics": true
265            }
266        }"#;
267
268        let member: Member = serde_json::from_str(json).unwrap();
269        assert_eq!(member.id, 123);
270        assert_eq!(member.name.as_deref(), Some("John Doe"));
271        assert_eq!(member.role.as_deref(), Some("admin"));
272        assert!(member.permissions.is_some());
273        let perms = member.permissions.unwrap();
274        assert_eq!(perms.manage_routes, Some(true));
275        assert_eq!(perms.manage_members, Some(false));
276    }
277
278    #[test]
279    fn test_update_member_request_serialization() {
280        let req = UpdateMemberRequest {
281            role: Some("moderator".to_string()),
282            status: Some("active".to_string()),
283            permissions: Some(MemberPermissions {
284                manage_routes: Some(true),
285                manage_events: Some(false),
286                manage_members: Some(false),
287                view_analytics: Some(true),
288            }),
289        };
290
291        let json = serde_json::to_value(&req).unwrap();
292        assert_eq!(json.get("role").unwrap(), "moderator");
293        assert_eq!(json.get("status").unwrap(), "active");
294        assert!(json.get("permissions").is_some());
295    }
296
297    #[test]
298    fn test_member_wrapper_deserialization() {
299        let json = r#"{
300            "member": {
301                "id": 888,
302                "user_id": 777,
303                "name": "Wrapped Member",
304                "role": "admin",
305                "status": "active"
306            }
307        }"#;
308
309        #[derive(Deserialize)]
310        struct MemberWrapper {
311            member: Member,
312        }
313
314        let wrapper: MemberWrapper = serde_json::from_str(json).unwrap();
315        assert_eq!(wrapper.member.id, 888);
316        assert_eq!(wrapper.member.user_id, Some(777));
317        assert_eq!(wrapper.member.name.as_deref(), Some("Wrapped Member"));
318        assert_eq!(wrapper.member.role.as_deref(), Some("admin"));
319    }
320
321    #[test]
322    fn test_member_permissions_deserialization() {
323        let json = r#"{
324            "manage_routes": true,
325            "manage_events": false,
326            "manage_members": true,
327            "view_analytics": true
328        }"#;
329
330        let perms: MemberPermissions = serde_json::from_str(json).unwrap();
331        assert_eq!(perms.manage_routes, Some(true));
332        assert_eq!(perms.manage_events, Some(false));
333        assert_eq!(perms.manage_members, Some(true));
334        assert_eq!(perms.view_analytics, Some(true));
335    }
336
337    #[test]
338    fn test_member_with_user_object() {
339        let json = r#"{
340            "id": 999,
341            "user_id": 555,
342            "name": "Full Member",
343            "admin": true,
344            "manages_routes": true,
345            "user": {
346                "id": 555,
347                "name": "User Name",
348                "email": "user@example.com"
349            }
350        }"#;
351
352        let member: Member = serde_json::from_str(json).unwrap();
353        assert_eq!(member.id, 999);
354        assert_eq!(member.admin, Some(true));
355        assert!(member.user.is_some());
356        let user = member.user.unwrap();
357        assert_eq!(user.id, 555);
358        assert_eq!(user.name.as_deref(), Some("User Name"));
359    }
360}