dialtone_sqlx 0.1.0

Dialtone SQLx Back-End
Documentation
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 {
    // create private key
    let mut rng = thread_rng();
    let private_key = RsaPrivateKey::new(&mut rng, BITS).expect("failed to generate a key");

    // create public key
    let public_key = RsaPublicKey::from(&private_key);

    // create PEM versions of keys
    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");

    // create actor ID
    let id = create_actor_id(host_name, new_actor.preferred_user_name);

    // create JRD
    let jrd = create_jrd(
        host_name,
        new_actor.preferred_user_name,
        &id,
        &new_actor.icon,
    );

    // create back end actor
    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, //do this last because it is a move
            },
            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![
        // add activity pub link
        JrdLink {
            rel: SELF.to_string(),
            href: id.to_string(),
            media_type: Option::from(ACTIVITY_PUB_MEDIA_TYPE.to_string()),
        },
        // add activity streams link
        JrdLink {
            rel: SELF.to_string(),
            href: id.to_string(),
            media_type: Option::from(ACTIVITY_STREAMS_MEDIA_TYPE.to_string()),
        },
        // add link to profile page
        JrdLink {
            rel: PROFILE_PAGE_URL_ID.to_string(),
            href: id.to_string(),
            media_type: Option::from(HTML_MEDIA_TYPE.to_string()),
        },
    ];

    // add the first icon
    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() {
        // GIVEN
        let new_actor = NewActor {
            preferred_user_name: "testuser",
            actor_type: ActorType::Person,
            owner: "user@example.com",
            name: None,
            summary: None,
            icon: None,
            image: None,
        };

        // WHEN
        super::new_actor("example.com", &new_actor);

        // THEN
    }

    #[test]
    fn GIVEN_new_actor_WHEN_serialized_THEN_no_panics() {
        // GIVEN
        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);

        // WHEN
        let json = serde_json::to_string(&actor.owned_actor);

        // THEN
        assert!(json.is_ok());
    }

    #[test]
    fn GIVEN_ap_object_WHEN_convert_to_banner_THEN_attributes_are_correct() {
        // GIVEN
        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,
        };

        // WHEN
        let banner = banner_from_ap_object(Some(ap_object));

        // THEN
        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() {
        // GIVEN
        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,
        };

        // WHEN
        let icon = icon_from_ap_object(Some(ap_object));

        // THEN
        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());
    }
}