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