Skip to main content

mimick/api_client/
people.rs

1use super::{ImmichApiClient, Person};
2
3#[derive(Debug, serde::Deserialize)]
4struct PersonStatistics {
5    assets: u64,
6}
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct PersonUpdate {
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub name: Option<String>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub is_hidden: Option<bool>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub birth_date: Option<Option<String>>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub is_favorite: Option<bool>,
19}
20
21impl ImmichApiClient {
22    pub async fn update_person(
23        &self,
24        person_id: &str,
25        update: &PersonUpdate,
26    ) -> Result<(), String> {
27        let body = serde_json::to_value(update).map_err(|err| err.to_string())?;
28        let path = format!("/api/people/{person_id}");
29        self.send_json(reqwest::Method::PUT, &path, Some(body))
30            .await
31            .map(|_| ())
32    }
33
34    pub async fn fetch_person(&self, person_id: &str) -> Result<Person, String> {
35        let path = format!("/api/people/{person_id}");
36        let resp = self.send_json(reqwest::Method::GET, &path, None).await?;
37        resp.json().await.map_err(|err| err.to_string())
38    }
39
40    pub async fn person_asset_count(&self, person_id: &str) -> Result<u64, String> {
41        let path = format!("/api/people/{person_id}/statistics");
42        let resp = self.send_json(reqwest::Method::GET, &path, None).await?;
43        let stats: PersonStatistics = resp.json().await.map_err(|err| err.to_string())?;
44        Ok(stats.assets)
45    }
46
47    pub async fn merge_people(&self, into_id: &str, merge_ids: &[String]) -> Result<(), String> {
48        let path = format!("/api/people/{into_id}/merge");
49        let body = serde_json::json!({ "ids": merge_ids });
50        self.send_json(reqwest::Method::POST, &path, Some(body))
51            .await
52            .map(|_| ())
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn update_sends_only_set_fields() {
62        let rename = PersonUpdate {
63            name: Some("Ana".into()),
64            ..PersonUpdate::default()
65        };
66        assert_eq!(
67            serde_json::to_value(&rename).unwrap(),
68            serde_json::json!({ "name": "Ana" })
69        );
70    }
71
72    #[test]
73    fn clearing_a_birthday_sends_null() {
74        let clear = PersonUpdate {
75            birth_date: Some(None),
76            ..PersonUpdate::default()
77        };
78        assert_eq!(
79            serde_json::to_value(&clear).unwrap(),
80            serde_json::json!({ "birthDate": null })
81        );
82    }
83
84    #[test]
85    fn favorite_update_sends_is_favorite() {
86        let favorite = PersonUpdate {
87            is_favorite: Some(true),
88            ..PersonUpdate::default()
89        };
90        assert_eq!(
91            serde_json::to_value(&favorite).unwrap(),
92            serde_json::json!({ "isFavorite": true })
93        );
94    }
95
96    #[test]
97    fn person_json_reads_birth_date() {
98        let person: super::super::Person = serde_json::from_value(serde_json::json!({
99            "id": "p1",
100            "name": "Ana",
101            "isHidden": true,
102            "birthDate": "1990-05-04"
103        }))
104        .unwrap();
105        assert!(person.is_hidden);
106        assert_eq!(person.birth_date.as_deref(), Some("1990-05-04"));
107    }
108}