use cloudillo_core::App;
use cloudillo_types::{
meta_adapter::{Profile, ProfileConnectionStatus, ProfileType},
prelude::*,
};
use crate::types::{ContactInput, ContactName, ProfileOverlay};
pub async fn resolve_profile(
app: &App,
tn_id: TnId,
id_tag: &str,
) -> ClResult<Option<Profile<Box<str>>>> {
match app.meta_adapter.read_profile(tn_id, id_tag).await {
Ok((_etag, profile)) => Ok(Some(profile)),
Err(Error::NotFound) => Ok(None),
Err(e) => Err(e),
}
}
fn profile_pic_url(id_tag: &str, profile_pic: Option<&str>) -> Option<String> {
let pic = profile_pic?;
let encoded_pic = cloudillo_dav::urlencode_path(pic);
Some(format!("https://cl-o.{id_tag}/api/files/{encoded_pic}?variant=vis.sd"))
}
fn split_name(name: &str) -> (Option<String>, Option<String>) {
let trimmed = name.trim();
if trimmed.is_empty() {
return (None, None);
}
match trimmed.rsplit_once(char::is_whitespace) {
Some((given, family)) => (Some(given.to_string()), Some(family.to_string())),
None => (Some(trimmed.to_string()), None),
}
}
pub fn merge_profile_into_input(input: &mut ContactInput, profile: Option<&Profile<Box<str>>>) {
let Some(profile) = profile else { return };
if input.profile_id_tag.as_deref() != Some(profile.id_tag.as_ref()) {
return;
}
if input.formatted_name.as_deref().is_none_or(str::is_empty) {
input.formatted_name = Some(profile.name.to_string());
}
if input.n.is_none() && matches!(profile.typ, ProfileType::Person) {
let (given, family) = split_name(profile.name.as_ref());
if given.is_some() || family.is_some() {
input.n = Some(ContactName { given, family, ..Default::default() });
}
}
if input.photo.as_deref().is_none_or(str::is_empty)
&& let Some(url) = profile_pic_url(profile.id_tag.as_ref(), profile.profile_pic.as_deref())
{
input.photo = Some(url);
}
}
pub fn build_overlay(profile: &Profile<Box<str>>) -> ProfileOverlay {
let r#type = match profile.typ {
ProfileType::Person => "person",
ProfileType::Community => "community",
};
ProfileOverlay {
id_tag: profile.id_tag.to_string(),
name: Some(profile.name.to_string()),
r#type: Some(r#type.to_string()),
profile_pic: profile_pic_url(profile.id_tag.as_ref(), profile.profile_pic.as_deref()),
status: profile.status,
connected: Some(matches!(profile.connected, ProfileConnectionStatus::Connected)),
following: Some(profile.following),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn profile(name: &str, pic: Option<&str>) -> Profile<Box<str>> {
Profile {
id_tag: "alice@example.com".into(),
name: name.into(),
typ: ProfileType::Person,
profile_pic: pic.map(Into::into),
status: None,
synced_at: None,
following: true,
follower: true,
connected: ProfileConnectionStatus::Connected,
roles: None,
trust: None,
}
}
#[test]
fn fills_missing_fn_and_photo() {
let mut input =
ContactInput { profile_id_tag: Some("alice@example.com".into()), ..Default::default() };
merge_profile_into_input(&mut input, Some(&profile("Alice Doe", Some("f1~abc"))));
assert_eq!(input.formatted_name.as_deref(), Some("Alice Doe"));
assert_eq!(
input.photo.as_deref(),
Some("https://cl-o.alice@example.com/api/files/f1~abc?variant=vis.sd"),
);
let n = input.n.expect("N filled from name split");
assert_eq!(n.given.as_deref(), Some("Alice"));
assert_eq!(n.family.as_deref(), Some("Doe"));
}
#[test]
fn explicit_values_win() {
let mut input = ContactInput {
profile_id_tag: Some("alice@example.com".into()),
formatted_name: Some("Alice At Work".into()),
photo: Some("https://custom.example/photo.jpg".into()),
..Default::default()
};
merge_profile_into_input(&mut input, Some(&profile("Alice Doe", Some("f1~abc"))));
assert_eq!(input.formatted_name.as_deref(), Some("Alice At Work"));
assert_eq!(input.photo.as_deref(), Some("https://custom.example/photo.jpg"));
}
#[test]
fn no_merge_without_profile() {
let mut input =
ContactInput { profile_id_tag: Some("alice@example.com".into()), ..Default::default() };
merge_profile_into_input(&mut input, None);
assert!(input.formatted_name.is_none());
}
#[test]
fn no_merge_when_id_tag_mismatches() {
let mut input =
ContactInput { profile_id_tag: Some("bob@example.com".into()), ..Default::default() };
merge_profile_into_input(&mut input, Some(&profile("Alice Doe", None)));
assert!(input.formatted_name.is_none());
}
}