1use crate::auth::TokenManager;
2use crate::config::Config;
3use crate::errors::{PeopleApiError, PeopleResult};
4use log::{debug, error};
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use tokio::sync::Mutex;
9
10const PEOPLE_API_BASE_URL: &str = "https://people.googleapis.com/v1";
11
12type Result<T> = PeopleResult<T>;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Contact {
18 pub resource_name: String,
19 pub name: Option<PersonName>,
20 pub email_addresses: Vec<EmailAddress>,
21 pub phone_numbers: Vec<PhoneNumber>,
22 pub organizations: Vec<Organization>,
23 pub photos: Vec<Photo>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PersonName {
28 pub display_name: String,
29 pub given_name: Option<String>,
30 pub family_name: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct EmailAddress {
35 pub value: String,
36 pub type_: Option<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PhoneNumber {
41 pub value: String,
42 pub type_: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Organization {
47 pub name: Option<String>,
48 pub title: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Photo {
53 pub url: String,
54 pub default: bool,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ContactList {
59 pub contacts: Vec<Contact>,
60 pub next_page_token: Option<String>,
61 pub total_items: Option<u32>,
62}
63
64#[derive(Debug, Clone)]
66pub struct PeopleClient {
67 client: Client,
68 token_manager: Arc<Mutex<TokenManager>>,
69}
70
71impl PeopleClient {
72 pub fn new(config: &Config) -> Self {
73 let client = Client::new();
74 let token_manager = Arc::new(Mutex::new(TokenManager::new(config)));
76
77 Self {
78 client,
79 token_manager,
80 }
81 }
82
83 pub async fn list_contacts(&self, max_results: Option<u32>) -> Result<ContactList> {
85 let token = self
86 .token_manager
87 .lock()
88 .await
89 .get_token(&self.client)
90 .await
91 .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
92
93 let mut url = format!("{}/people/me/connections", PEOPLE_API_BASE_URL);
94
95 let mut query_parts = Vec::new();
97
98 let fields = [
100 "names",
101 "emailAddresses",
102 "phoneNumbers",
103 "organizations",
104 "photos",
105 ];
106 query_parts.push(format!("personFields={}", fields.join(",")));
107
108 if let Some(max) = max_results {
109 query_parts.push(format!("pageSize={}", max));
110 }
111
112 if !query_parts.is_empty() {
113 url = format!("{}?{}", url, query_parts.join("&"));
114 }
115
116 debug!("Listing contacts from: {}", url);
117
118 let response = self
119 .client
120 .get(&url)
121 .header("Authorization", format!("Bearer {}", token))
122 .send()
123 .await
124 .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
125
126 let status = response.status();
127 if !status.is_success() {
128 let error_text = response
129 .text()
130 .await
131 .unwrap_or_else(|_| "<no response body>".to_string());
132 return Err(PeopleApiError::ApiError(format!(
133 "Failed to list contacts. Status: {}, Error: {}",
134 status, error_text
135 )));
136 }
137
138 let json_response = response
139 .json::<serde_json::Value>()
140 .await
141 .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
142
143 let mut contacts = Vec::new();
144
145 if let Some(connections) = json_response.get("connections").and_then(|v| v.as_array()) {
146 for connection in connections {
147 if let Ok(contact) = self.parse_contact(connection) {
148 contacts.push(contact);
149 } else {
150 error!("Failed to parse contact: {:?}", connection);
152 }
153 }
154 }
155
156 let next_page_token = json_response
157 .get("nextPageToken")
158 .and_then(|v| v.as_str())
159 .map(|s| s.to_string());
160
161 let total_items = json_response
162 .get("totalItems")
163 .and_then(|v| v.as_u64())
164 .map(|n| n as u32);
165
166 Ok(ContactList {
167 contacts,
168 next_page_token,
169 total_items,
170 })
171 }
172
173 pub async fn search_contacts(
175 &self,
176 query: &str,
177 max_results: Option<u32>,
178 ) -> Result<ContactList> {
179 let token = self
180 .token_manager
181 .lock()
182 .await
183 .get_token(&self.client)
184 .await
185 .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
186
187 let mut url = format!("{}/people:searchContacts", PEOPLE_API_BASE_URL);
188
189 let mut query_parts = Vec::new();
191
192 query_parts.push(format!("query={}", query));
194
195 let fields = [
197 "names",
198 "emailAddresses",
199 "phoneNumbers",
200 "organizations",
201 "photos",
202 ];
203 query_parts.push(format!("readMask={}", fields.join(",")));
204
205 if let Some(max) = max_results {
206 query_parts.push(format!("pageSize={}", max));
207 }
208
209 if !query_parts.is_empty() {
210 url = format!("{}?{}", url, query_parts.join("&"));
211 }
212
213 debug!("Searching contacts: {}", url);
214
215 let response = self
216 .client
217 .get(&url)
218 .header("Authorization", format!("Bearer {}", token))
219 .send()
220 .await
221 .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
222
223 let status = response.status();
224 if !status.is_success() {
225 let error_text = response
226 .text()
227 .await
228 .unwrap_or_else(|_| "<no response body>".to_string());
229 return Err(PeopleApiError::ApiError(format!(
230 "Failed to search contacts. Status: {}, Error: {}",
231 status, error_text
232 )));
233 }
234
235 let json_response = response
236 .json::<serde_json::Value>()
237 .await
238 .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
239
240 let mut contacts = Vec::new();
241
242 if let Some(results) = json_response.get("results").and_then(|v| v.as_array()) {
243 for result in results {
244 if let Some(person) = result.get("person") {
245 if let Ok(contact) = self.parse_contact(person) {
246 contacts.push(contact);
247 } else {
248 error!("Failed to parse contact: {:?}", person);
250 }
251 }
252 }
253 }
254
255 let next_page_token = json_response
256 .get("nextPageToken")
257 .and_then(|v| v.as_str())
258 .map(|s| s.to_string());
259
260 let total_items = json_response
261 .get("totalPeople")
262 .and_then(|v| v.as_u64())
263 .map(|n| n as u32);
264
265 Ok(ContactList {
266 contacts,
267 next_page_token,
268 total_items,
269 })
270 }
271
272 pub async fn get_contact(&self, resource_name: &str) -> Result<Contact> {
274 let token = self
275 .token_manager
276 .lock()
277 .await
278 .get_token(&self.client)
279 .await
280 .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
281
282 let mut url = format!("{}/{}", PEOPLE_API_BASE_URL, resource_name);
283
284 let fields = [
286 "names",
287 "emailAddresses",
288 "phoneNumbers",
289 "organizations",
290 "photos",
291 ];
292 url = format!("{}?personFields={}", url, fields.join(","));
293
294 debug!("Getting contact: {}", url);
295
296 let response = self
297 .client
298 .get(&url)
299 .header("Authorization", format!("Bearer {}", token))
300 .send()
301 .await
302 .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
303
304 let status = response.status();
305 if !status.is_success() {
306 let error_text = response
307 .text()
308 .await
309 .unwrap_or_else(|_| "<no response body>".to_string());
310 return Err(PeopleApiError::ApiError(format!(
311 "Failed to get contact. Status: {}, Error: {}",
312 status, error_text
313 )));
314 }
315
316 let json_response = response
317 .json::<serde_json::Value>()
318 .await
319 .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
320
321 self.parse_contact(&json_response)
322 }
323
324 fn parse_contact(&self, data: &serde_json::Value) -> Result<Contact> {
326 let resource_name = data
327 .get("resourceName")
328 .and_then(|v| v.as_str())
329 .ok_or_else(|| PeopleApiError::ParseError("Missing resourceName".to_string()))?
330 .to_string();
331
332 let name = if let Some(names) = data.get("names").and_then(|v| v.as_array()) {
334 if let Some(primary_name) = names.first() {
335 let display_name = primary_name
336 .get("displayName")
337 .and_then(|v| v.as_str())
338 .unwrap_or("Unknown")
339 .to_string();
340
341 let given_name = primary_name
342 .get("givenName")
343 .and_then(|v| v.as_str())
344 .map(|s| s.to_string());
345
346 let family_name = primary_name
347 .get("familyName")
348 .and_then(|v| v.as_str())
349 .map(|s| s.to_string());
350
351 Some(PersonName {
352 display_name,
353 given_name,
354 family_name,
355 })
356 } else {
357 None
358 }
359 } else {
360 None
361 };
362
363 let mut email_addresses = Vec::new();
365 if let Some(emails) = data.get("emailAddresses").and_then(|v| v.as_array()) {
366 for email in emails {
367 if let Some(value) = email.get("value").and_then(|v| v.as_str()) {
368 let type_ = email
369 .get("type")
370 .and_then(|v| v.as_str())
371 .map(|s| s.to_string());
372
373 email_addresses.push(EmailAddress {
374 value: value.to_string(),
375 type_,
376 });
377 }
378 }
379 }
380
381 let mut phone_numbers = Vec::new();
383 if let Some(phones) = data.get("phoneNumbers").and_then(|v| v.as_array()) {
384 for phone in phones {
385 if let Some(value) = phone.get("value").and_then(|v| v.as_str()) {
386 let type_ = phone
387 .get("type")
388 .and_then(|v| v.as_str())
389 .map(|s| s.to_string());
390
391 phone_numbers.push(PhoneNumber {
392 value: value.to_string(),
393 type_,
394 });
395 }
396 }
397 }
398
399 let mut organizations = Vec::new();
401 if let Some(orgs) = data.get("organizations").and_then(|v| v.as_array()) {
402 for org in orgs {
403 let name = org
404 .get("name")
405 .and_then(|v| v.as_str())
406 .map(|s| s.to_string());
407
408 let title = org
409 .get("title")
410 .and_then(|v| v.as_str())
411 .map(|s| s.to_string());
412
413 organizations.push(Organization { name, title });
414 }
415 }
416
417 let mut photos = Vec::new();
419 if let Some(pics) = data.get("photos").and_then(|v| v.as_array()) {
420 for pic in pics {
421 if let Some(url) = pic.get("url").and_then(|v| v.as_str()) {
422 let default = pic
423 .get("default")
424 .and_then(|v| v.as_bool())
425 .unwrap_or(false);
426
427 photos.push(Photo {
428 url: url.to_string(),
429 default,
430 });
431 }
432 }
433 }
434
435 Ok(Contact {
436 resource_name,
437 name,
438 email_addresses,
439 phone_numbers,
440 organizations,
441 photos,
442 })
443 }
444}