Save extra fields from remote actors
This commit is contained in:
parent
6dec1a5da1
commit
7fad429a8c
8 changed files with 60 additions and 16 deletions
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::HttpError;
|
use crate::models::profiles::types::ExtraField;
|
||||||
use crate::models::users::types::User;
|
use crate::models::users::types::User;
|
||||||
use crate::utils::crypto::{deserialize_private_key, get_public_key_pem};
|
use crate::utils::crypto::{deserialize_private_key, get_public_key_pem};
|
||||||
use crate::utils::files::get_file_url;
|
use crate::utils::files::get_file_url;
|
||||||
|
@ -14,7 +14,7 @@ use super::views::{
|
||||||
get_followers_url,
|
get_followers_url,
|
||||||
get_following_url,
|
get_following_url,
|
||||||
};
|
};
|
||||||
use super::vocabulary::{PERSON, IMAGE};
|
use super::vocabulary::{PERSON, IMAGE, PROPERTY_VALUE};
|
||||||
|
|
||||||
const W3ID_CONTEXT: &str = "https://w3id.org/security/v1";
|
const W3ID_CONTEXT: &str = "https://w3id.org/security/v1";
|
||||||
|
|
||||||
|
@ -40,6 +40,14 @@ pub struct ActorCapabilities {
|
||||||
accepts_chat_messages: Option<bool>,
|
accepts_chat_messages: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct ActorProperty {
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
object_type: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Actor {
|
pub struct Actor {
|
||||||
|
@ -71,12 +79,31 @@ pub struct Actor {
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub summary: Option<String>,
|
pub summary: Option<String>,
|
||||||
|
|
||||||
|
pub attachment: Option<Vec<ActorProperty>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor {
|
||||||
|
/// Parse 'attachment' into ExtraField vector
|
||||||
|
pub fn extra_fields(&self) -> Vec<ExtraField> {
|
||||||
|
match &self.attachment {
|
||||||
|
Some(properties) => {
|
||||||
|
properties.iter()
|
||||||
|
.map(|prop| ExtraField {
|
||||||
|
name: prop.name.clone(),
|
||||||
|
value: prop.value.clone(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
None => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_actor_object(
|
pub fn get_actor_object(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
user: &User,
|
user: &User,
|
||||||
) -> Result<Actor, HttpError> {
|
) -> Result<Actor, rsa::pkcs8::Error> {
|
||||||
let username = &user.profile.username;
|
let username = &user.profile.username;
|
||||||
let id = get_actor_url(&config.instance_url(), &username);
|
let id = get_actor_url(&config.instance_url(), &username);
|
||||||
let inbox = get_inbox_url(&config.instance_url(), &username);
|
let inbox = get_inbox_url(&config.instance_url(), &username);
|
||||||
|
@ -84,15 +111,16 @@ pub fn get_actor_object(
|
||||||
let followers = get_followers_url(&config.instance_url(), &username);
|
let followers = get_followers_url(&config.instance_url(), &username);
|
||||||
let following = get_following_url(&config.instance_url(), &username);
|
let following = get_following_url(&config.instance_url(), &username);
|
||||||
|
|
||||||
let private_key = deserialize_private_key(&user.private_key)
|
let private_key = deserialize_private_key(&user.private_key)?;
|
||||||
.map_err(|_| HttpError::InternalError)?;
|
let public_key_pem = get_public_key_pem(&private_key)?;
|
||||||
let public_key_pem = get_public_key_pem(&private_key)
|
|
||||||
.map_err(|_| HttpError::InternalError)?;
|
|
||||||
let public_key = PublicKey {
|
let public_key = PublicKey {
|
||||||
id: format!("{}#main-key", id),
|
id: format!("{}#main-key", id),
|
||||||
owner: id.clone(),
|
owner: id.clone(),
|
||||||
public_key_pem: public_key_pem,
|
public_key_pem: public_key_pem,
|
||||||
};
|
};
|
||||||
|
let capabilities = ActorCapabilities {
|
||||||
|
accepts_chat_messages: Some(false),
|
||||||
|
};
|
||||||
let avatar = match &user.profile.avatar_file_name {
|
let avatar = match &user.profile.avatar_file_name {
|
||||||
Some(file_name) => {
|
Some(file_name) => {
|
||||||
let image = Image {
|
let image = Image {
|
||||||
|
@ -113,9 +141,15 @@ pub fn get_actor_object(
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let capabilities = ActorCapabilities {
|
let properties = user.profile.extra_fields.clone()
|
||||||
accepts_chat_messages: Some(false),
|
.unpack().into_iter()
|
||||||
};
|
.map(|field| {
|
||||||
|
ActorProperty {
|
||||||
|
object_type: PROPERTY_VALUE.to_string(),
|
||||||
|
name: field.name,
|
||||||
|
value: field.value,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
let actor = Actor {
|
let actor = Actor {
|
||||||
context: Some(json!([
|
context: Some(json!([
|
||||||
AP_CONTEXT.to_string(),
|
AP_CONTEXT.to_string(),
|
||||||
|
@ -134,6 +168,7 @@ pub fn get_actor_object(
|
||||||
icon: avatar,
|
icon: avatar,
|
||||||
image: banner,
|
image: banner,
|
||||||
summary: None,
|
summary: None,
|
||||||
|
attachment: Some(properties),
|
||||||
};
|
};
|
||||||
Ok(actor)
|
Ok(actor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ pub async fn fetch_profile_by_actor_id(
|
||||||
let actor_value: Value = serde_json::from_str(&actor_json)?;
|
let actor_value: Value = serde_json::from_str(&actor_json)?;
|
||||||
let actor: Actor = serde_json::from_value(actor_value.clone())?;
|
let actor: Actor = serde_json::from_value(actor_value.clone())?;
|
||||||
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;
|
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;
|
||||||
|
let extra_fields = actor.extra_fields();
|
||||||
let actor_address = format!(
|
let actor_address = format!(
|
||||||
"{}@{}",
|
"{}@{}",
|
||||||
actor.preferred_username,
|
actor.preferred_username,
|
||||||
|
@ -102,8 +103,9 @@ pub async fn fetch_profile_by_actor_id(
|
||||||
display_name: Some(actor.name),
|
display_name: Some(actor.name),
|
||||||
acct: actor_address,
|
acct: actor_address,
|
||||||
bio: actor.summary,
|
bio: actor.summary,
|
||||||
avatar: avatar,
|
avatar,
|
||||||
banner: banner,
|
banner,
|
||||||
|
extra_fields,
|
||||||
actor: Some(actor_value),
|
actor: Some(actor_value),
|
||||||
};
|
};
|
||||||
Ok(profile_data)
|
Ok(profile_data)
|
||||||
|
|
|
@ -146,13 +146,14 @@ pub async fn receive_activity(
|
||||||
let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
|
let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
|
||||||
let (avatar, banner) = fetch_avatar_and_banner(&actor, &config.media_dir()).await
|
let (avatar, banner) = fetch_avatar_and_banner(&actor, &config.media_dir()).await
|
||||||
.map_err(|_| ValidationError("failed to fetch image"))?;
|
.map_err(|_| ValidationError("failed to fetch image"))?;
|
||||||
|
let extra_fields = actor.extra_fields();
|
||||||
let mut profile_data = ProfileUpdateData {
|
let mut profile_data = ProfileUpdateData {
|
||||||
display_name: Some(actor.name),
|
display_name: Some(actor.name),
|
||||||
bio: actor.summary.clone(),
|
bio: actor.summary.clone(),
|
||||||
bio_source: actor.summary,
|
bio_source: actor.summary,
|
||||||
avatar,
|
avatar,
|
||||||
banner,
|
banner,
|
||||||
extra_fields: vec![],
|
extra_fields,
|
||||||
};
|
};
|
||||||
profile_data.clean()?;
|
profile_data.clean()?;
|
||||||
update_profile(db_client, &profile.id, profile_data).await?;
|
update_profile(db_client, &profile.id, profile_data).await?;
|
||||||
|
|
|
@ -47,7 +47,8 @@ async fn get_actor(
|
||||||
) -> Result<HttpResponse, HttpError> {
|
) -> Result<HttpResponse, HttpError> {
|
||||||
let db_client = &**get_database_client(&db_pool).await?;
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
let user = get_user_by_name(db_client, &username).await?;
|
let user = get_user_by_name(db_client, &username).await?;
|
||||||
let actor = get_actor_object(&config, &user)?;
|
let actor = get_actor_object(&config, &user)
|
||||||
|
.map_err(|_| HttpError::InternalError)?;
|
||||||
let response = HttpResponse::Ok()
|
let response = HttpResponse::Ok()
|
||||||
.content_type(ACTIVITY_CONTENT_TYPE)
|
.content_type(ACTIVITY_CONTENT_TYPE)
|
||||||
.json(actor);
|
.json(actor);
|
||||||
|
|
|
@ -12,3 +12,4 @@ pub const PERSON: &str = "Person";
|
||||||
pub const DOCUMENT: &str = "Document";
|
pub const DOCUMENT: &str = "Document";
|
||||||
pub const IMAGE: &str = "Image";
|
pub const IMAGE: &str = "Image";
|
||||||
pub const NOTE: &str = "Note";
|
pub const NOTE: &str = "Note";
|
||||||
|
pub const PROPERTY_VALUE: &str = "PropertyValue";
|
||||||
|
|
|
@ -15,14 +15,15 @@ pub async fn create_profile(
|
||||||
profile_data: &ProfileCreateData,
|
profile_data: &ProfileCreateData,
|
||||||
) -> Result<DbActorProfile, DatabaseError> {
|
) -> Result<DbActorProfile, DatabaseError> {
|
||||||
let profile_id = Uuid::new_v4();
|
let profile_id = Uuid::new_v4();
|
||||||
|
let extra_fields = ExtraFields(profile_data.extra_fields.clone());
|
||||||
let result = db_client.query_one(
|
let result = db_client.query_one(
|
||||||
"
|
"
|
||||||
INSERT INTO actor_profile (
|
INSERT INTO actor_profile (
|
||||||
id, username, display_name, acct, bio, bio_source,
|
id, username, display_name, acct, bio, bio_source,
|
||||||
avatar_file_name, banner_file_name,
|
avatar_file_name, banner_file_name, extra_fields,
|
||||||
actor_json
|
actor_json
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
RETURNING actor_profile
|
RETURNING actor_profile
|
||||||
",
|
",
|
||||||
&[
|
&[
|
||||||
|
@ -34,6 +35,7 @@ pub async fn create_profile(
|
||||||
&profile_data.bio,
|
&profile_data.bio,
|
||||||
&profile_data.avatar,
|
&profile_data.avatar,
|
||||||
&profile_data.banner,
|
&profile_data.banner,
|
||||||
|
&extra_fields,
|
||||||
&profile_data.actor,
|
&profile_data.actor,
|
||||||
],
|
],
|
||||||
).await;
|
).await;
|
||||||
|
|
|
@ -74,6 +74,7 @@ pub struct ProfileCreateData {
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
|
pub extra_fields: Vec<ExtraField>,
|
||||||
pub actor: Option<Value>,
|
pub actor: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,7 @@ pub async fn create_user(
|
||||||
bio: None,
|
bio: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
banner: None,
|
banner: None,
|
||||||
|
extra_fields: vec![],
|
||||||
actor: None,
|
actor: None,
|
||||||
};
|
};
|
||||||
let profile = create_profile(&transaction, &profile_data).await?;
|
let profile = create_profile(&transaction, &profile_data).await?;
|
||||||
|
|
Loading…
Reference in a new issue