use dialtone_common::ap::ap_object::{ApObject, ApObjectType};
use rand::thread_rng;
use rsa::{pkcs1::EncodeRsaPrivateKey, pkcs1::EncodeRsaPublicKey, RsaPrivateKey, RsaPublicKey};
use sqlx::types::chrono::Utc;
use dialtone_common::ap::image_attr::{ImageAttributeObj, ImageAttributeType, ImageAttributes};
use dialtone_common::ap::{
create_actor_id, create_followers, create_following, create_inbox, create_liked, create_likes,
create_outbox, create_shared_inbox, ActorType, Endpoints,
};
use dialtone_common::containers::credentialed_actor::CredentialedActor;
use dialtone_common::pages::HTML_MEDIA_TYPE;
use dialtone_common::rest::actors::actor_model::OwnedActor;
use dialtone_common::utils::media_types::{ACTIVITY_PUB_MEDIA_TYPE, ACTIVITY_STREAMS_MEDIA_TYPE};
use dialtone_common::webfinger::{
create_acct, Jrd, JrdLink, AVATAR_REL_URL_ID, PROFILE_PAGE_URL_ID, SELF,
};
use serde_variant::to_variant_name;
const BITS: usize = 2048;
const KEY_ID: &str = "mykey";
pub struct NewActor<'a> {
pub preferred_user_name: &'a str,
pub actor_type: ActorType,
pub owner: &'a str,
pub name: Option<&'a str>,
pub summary: Option<&'a str>,
pub icon: Option<&'a ImageAttributes>,
pub image: Option<&'a ImageAttributes>,
}
pub fn new_actor(host_name: &str, new_actor: &NewActor) -> CredentialedActor {
let mut rng = thread_rng();
let private_key = RsaPrivateKey::new(&mut rng, BITS).expect("failed to generate a key");
let public_key = RsaPublicKey::from(&private_key);
let private_key_pem = private_key
.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)
.expect("failed to create private key pem");
let public_key_pem = public_key
.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)
.expect("failed to create public key pem");
let id = create_actor_id(host_name, new_actor.preferred_user_name);
let jrd = create_jrd(
host_name,
new_actor.preferred_user_name,
&id,
&new_actor.icon,
);
CredentialedActor {
owned_actor: OwnedActor {
ap: dialtone_common::ap::Actor {
ap_type: new_actor.actor_type,
preferred_user_name: new_actor.preferred_user_name.to_string(),
name: new_actor.name.map(str::to_string),
summary: new_actor.summary.map(str::to_string),
icon: new_actor.icon.map(|i| i.to_owned()),
image: new_actor.image.map(|i| i.to_owned()),
following: Option::from(create_following(&id)),
followers: Option::from(create_followers(&id)),
liked: Option::from(create_liked(&id)),
likes: Option::from(create_likes(&id)),
inbox: Option::from(create_inbox(&id)),
outbox: Option::from(create_outbox(&id)),
public_key: dialtone_common::ap::PublicKey {
id: format!("{}#{}", id, KEY_ID),
owner: id.clone(),
public_key_pem,
},
endpoints: Option::from(Endpoints {
shared_inbox: Option::from(create_shared_inbox(host_name)),
}),
id, },
jrd: Option::from(jrd),
owner_data: None,
created_at: Utc::now(),
modified_at: Utc::now(),
},
creating_user_acct: new_actor.owner.to_owned(),
private_key_pem: private_key_pem.to_string(),
}
}
fn create_jrd(
host_name: &str,
preferred_user_name: &str,
id: &str,
icon: &Option<&ImageAttributes>,
) -> Jrd {
let mut links: Vec<JrdLink> = vec![
JrdLink {
rel: SELF.to_string(),
href: id.to_string(),
media_type: Option::from(ACTIVITY_PUB_MEDIA_TYPE.to_string()),
},
JrdLink {
rel: SELF.to_string(),
href: id.to_string(),
media_type: Option::from(ACTIVITY_STREAMS_MEDIA_TYPE.to_string()),
},
JrdLink {
rel: PROFILE_PAGE_URL_ID.to_string(),
href: id.to_string(),
media_type: Option::from(HTML_MEDIA_TYPE.to_string()),
},
];
if let Some(i) = icon {
if let Some(o) = i.first() {
links.push(JrdLink {
rel: AVATAR_REL_URL_ID.to_string(),
href: o.url,
media_type: o.media_type,
});
}
}
Jrd {
subject: create_acct(host_name, preferred_user_name),
links,
}
}
pub fn banner_from_ap_object(banner: Option<ApObject>) -> Option<ImageAttributes> {
if let Some(banner) = banner {
if banner.ap_type == Some(ApObjectType::Image)
&& banner.url.is_some()
&& banner.media_type.is_some()
{
let media_type = Some(
to_variant_name(&banner.media_type.unwrap())
.unwrap()
.to_string(),
);
let image_attributes = ImageAttributes::SingleObj(ImageAttributeObj {
summary: Some("Banner Image".to_string()),
url: banner.url.unwrap(),
width: None,
height: None,
media_type,
ap_type: Some(ImageAttributeType::Image),
});
Some(image_attributes)
} else {
None
}
} else {
None
}
}
pub fn icon_from_ap_object(icon: Option<ApObject>) -> Option<ImageAttributes> {
if let Some(icon) = icon {
if icon.ap_type == Some(ApObjectType::Image)
&& icon.url.is_some()
&& icon.media_type.is_some()
{
let media_type = Some(
to_variant_name(&icon.media_type.unwrap())
.unwrap()
.to_string(),
);
let image_attributes = ImageAttributes::SingleObj(ImageAttributeObj {
summary: Some("Note (400x400)".to_string()),
url: icon.url.unwrap(),
width: Some(400),
height: Some(400),
media_type,
ap_type: Some(ImageAttributeType::Image),
});
Some(image_attributes)
} else {
None
}
} else {
None
}
}
pub fn new_actor_with_images<'a>(
new_actor: &NewActor<'a>,
icon: &'a Option<ImageAttributes>,
banner: &'a Option<ImageAttributes>,
) -> NewActor<'a> {
NewActor {
icon: icon.as_ref(),
image: banner.as_ref(),
..*new_actor
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod new_actor_tests {
use dialtone_common::ap::{ap_object::ApObjectMediaType, ActorType};
use super::*;
#[test]
fn GIVEN_new_actor_data_WHEN_new_THEN_no_panics() {
let new_actor = NewActor {
preferred_user_name: "testuser",
actor_type: ActorType::Person,
owner: "user@example.com",
name: None,
summary: None,
icon: None,
image: None,
};
super::new_actor("example.com", &new_actor);
}
#[test]
fn GIVEN_new_actor_WHEN_serialized_THEN_no_panics() {
let new_actor = NewActor {
preferred_user_name: "testuser",
actor_type: ActorType::Person,
owner: "user@example.com",
name: None,
summary: None,
icon: None,
image: None,
};
let actor = super::new_actor("example.com", &new_actor);
let json = serde_json::to_string(&actor.owned_actor);
assert!(json.is_ok());
}
#[test]
fn GIVEN_ap_object_WHEN_convert_to_banner_THEN_attributes_are_correct() {
let ap_object = ApObject {
name: None,
id: Some("https://example.com/foo".to_string()),
url: Some("https://example.com/media/foo.jpg".to_string()),
media_type: Some(ApObjectMediaType::ImageJpeg),
ap_type: Some(ApObjectType::Image),
content: None,
summary: None,
actor: None,
attributed_to: None,
to: None,
cc: None,
bto: None,
bcc: None,
};
let banner = banner_from_ap_object(Some(ap_object));
assert!(banner.is_some());
let banner = banner.unwrap();
assert_eq!(
banner.first().unwrap().ap_type,
Some(ImageAttributeType::Image)
);
assert_eq!(
banner.first().unwrap().url,
"https://example.com/media/foo.jpg".to_string()
);
assert!(banner.first().unwrap().media_type.is_some());
}
#[test]
fn GIVEN_ap_object_WHEN_convert_to_icon_THEN_attributes_are_correct() {
let ap_object = ApObject {
name: None,
id: Some("https://example.com/foo".to_string()),
url: Some("https://example.com/media/foo.jpg".to_string()),
media_type: Some(ApObjectMediaType::ImageJpeg),
ap_type: Some(ApObjectType::Image),
content: None,
summary: None,
actor: None,
attributed_to: None,
to: None,
cc: None,
bto: None,
bcc: None,
};
let icon = icon_from_ap_object(Some(ap_object));
assert!(icon.is_some());
let icon = icon.unwrap();
assert_eq!(
icon.first().unwrap().ap_type,
Some(ImageAttributeType::Image)
);
assert_eq!(
icon.first().unwrap().url,
"https://example.com/media/foo.jpg".to_string()
);
assert_eq!(icon.first().unwrap().width, Some(400));
assert_eq!(icon.first().unwrap().height, Some(400));
assert!(icon.first().unwrap().media_type.is_some());
}
}