2021-12-20 01:00:35 +00:00
|
|
|
use std::collections::HashMap;
|
2021-10-09 22:58:21 +00:00
|
|
|
|
2022-02-23 23:07:51 +00:00
|
|
|
use actix_web::HttpRequest;
|
2021-04-09 00:22:17 +00:00
|
|
|
use regex::Regex;
|
2022-04-13 22:30:41 +00:00
|
|
|
use serde::de::DeserializeOwned;
|
2021-04-09 00:22:17 +00:00
|
|
|
use serde_json::Value;
|
2021-10-09 22:58:21 +00:00
|
|
|
use tokio_postgres::GenericClient;
|
2021-04-09 00:22:17 +00:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-12-28 18:34:13 +00:00
|
|
|
use crate::config::Config;
|
2021-12-24 15:13:25 +00:00
|
|
|
use crate::errors::{ConversionError, DatabaseError, HttpError, ValidationError};
|
2022-02-23 23:07:51 +00:00
|
|
|
use crate::http_signatures::verify::verify_http_signature;
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::models::attachments::queries::create_attachment;
|
2021-12-19 22:15:14 +00:00
|
|
|
use crate::models::posts::mentions::mention_to_address;
|
2021-10-09 23:59:45 +00:00
|
|
|
use crate::models::posts::queries::{
|
|
|
|
create_post,
|
|
|
|
get_post_by_id,
|
|
|
|
get_post_by_object_id,
|
2021-11-12 14:51:03 +00:00
|
|
|
delete_post,
|
2021-10-09 23:59:45 +00:00
|
|
|
};
|
2021-12-07 23:28:58 +00:00
|
|
|
use crate::models::posts::tags::normalize_tag;
|
2022-02-13 17:55:35 +00:00
|
|
|
use crate::models::posts::types::{Post, PostCreateData, Visibility};
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::models::profiles::queries::{
|
|
|
|
get_profile_by_actor_id,
|
|
|
|
get_profile_by_acct,
|
|
|
|
update_profile,
|
|
|
|
};
|
2021-12-28 18:34:13 +00:00
|
|
|
use crate::models::profiles::types::ProfileUpdateData;
|
2021-12-15 01:00:42 +00:00
|
|
|
use crate::models::reactions::queries::{
|
|
|
|
create_reaction,
|
|
|
|
get_reaction_by_activity_id,
|
|
|
|
delete_reaction,
|
|
|
|
};
|
2021-09-20 21:06:48 +00:00
|
|
|
use crate::models::relationships::queries::{
|
|
|
|
follow_request_accepted,
|
|
|
|
follow_request_rejected,
|
2021-09-20 21:45:33 +00:00
|
|
|
follow,
|
2022-01-03 23:19:52 +00:00
|
|
|
get_follow_request_by_id,
|
2021-09-20 21:45:33 +00:00
|
|
|
unfollow,
|
2021-09-20 21:06:48 +00:00
|
|
|
};
|
2022-01-02 23:50:37 +00:00
|
|
|
use crate::models::users::queries::get_user_by_name;
|
2022-02-08 21:33:05 +00:00
|
|
|
use crate::utils::html::clean_html;
|
2022-04-13 22:30:41 +00:00
|
|
|
use super::activity::{
|
|
|
|
Activity,
|
|
|
|
Attachment,
|
|
|
|
Object,
|
|
|
|
create_activity_accept_follow,
|
|
|
|
};
|
2021-12-28 18:34:13 +00:00
|
|
|
use super::actor::Actor;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::deliverer::deliver_activity;
|
2021-12-28 18:34:13 +00:00
|
|
|
use super::fetcher::fetchers::{
|
2021-10-09 22:58:21 +00:00
|
|
|
fetch_avatar_and_banner,
|
|
|
|
fetch_attachment,
|
2021-10-09 23:59:45 +00:00
|
|
|
fetch_object,
|
2021-10-09 22:58:21 +00:00
|
|
|
};
|
2021-12-28 18:34:13 +00:00
|
|
|
use super::fetcher::helpers::{
|
2021-12-28 18:34:13 +00:00
|
|
|
get_or_import_profile_by_actor_id,
|
2021-12-28 18:34:13 +00:00
|
|
|
import_profile_by_actor_address,
|
|
|
|
ImportError,
|
|
|
|
};
|
2022-02-13 18:55:37 +00:00
|
|
|
use super::inbox::create::get_note_visibility;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::vocabulary::*;
|
|
|
|
|
2021-09-20 21:45:33 +00:00
|
|
|
fn parse_actor_id(
|
|
|
|
instance_url: &str,
|
|
|
|
actor_id: &str,
|
|
|
|
) -> Result<String, ValidationError> {
|
|
|
|
let url_regexp_str = format!(
|
|
|
|
"^{}/users/(?P<username>[0-9a-z_]+)$",
|
2022-04-13 22:30:41 +00:00
|
|
|
instance_url.replace('.', r"\."),
|
2021-09-20 21:45:33 +00:00
|
|
|
);
|
|
|
|
let url_regexp = Regex::new(&url_regexp_str)
|
|
|
|
.map_err(|_| ValidationError("error"))?;
|
2021-11-13 17:37:31 +00:00
|
|
|
let url_caps = url_regexp.captures(actor_id)
|
2021-04-09 00:22:17 +00:00
|
|
|
.ok_or(ValidationError("invalid actor ID"))?;
|
|
|
|
let username = url_caps.name("username")
|
|
|
|
.ok_or(ValidationError("invalid actor ID"))?
|
|
|
|
.as_str()
|
|
|
|
.to_owned();
|
|
|
|
Ok(username)
|
|
|
|
}
|
|
|
|
|
2021-09-20 21:45:33 +00:00
|
|
|
fn parse_object_id(
|
|
|
|
instance_url: &str,
|
|
|
|
object_id: &str,
|
|
|
|
) -> Result<Uuid, ValidationError> {
|
|
|
|
let url_regexp_str = format!(
|
|
|
|
"^{}/objects/(?P<uuid>[0-9a-f-]+)$",
|
2022-04-13 22:30:41 +00:00
|
|
|
instance_url.replace('.', r"\."),
|
2021-09-20 21:45:33 +00:00
|
|
|
);
|
|
|
|
let url_regexp = Regex::new(&url_regexp_str)
|
|
|
|
.map_err(|_| ValidationError("error"))?;
|
2021-11-13 17:37:31 +00:00
|
|
|
let url_caps = url_regexp.captures(object_id)
|
2021-04-09 00:22:17 +00:00
|
|
|
.ok_or(ValidationError("invalid object ID"))?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let internal_object_id: Uuid = url_caps.name("uuid")
|
2021-04-09 00:22:17 +00:00
|
|
|
.ok_or(ValidationError("invalid object ID"))?
|
|
|
|
.as_str().parse()
|
|
|
|
.map_err(|_| ValidationError("invalid object ID"))?;
|
2021-11-29 20:45:36 +00:00
|
|
|
Ok(internal_object_id)
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 22:30:41 +00:00
|
|
|
/// Transforms arbitrary property value into array of strings
|
2021-12-24 15:13:25 +00:00
|
|
|
fn parse_array(value: &Value) -> Result<Vec<String>, ConversionError> {
|
2021-11-18 23:24:13 +00:00
|
|
|
let result = match value {
|
|
|
|
Value::String(string) => vec![string.to_string()],
|
|
|
|
Value::Array(array) => {
|
2021-12-24 15:13:25 +00:00
|
|
|
let mut results = vec![];
|
|
|
|
for value in array {
|
|
|
|
match value {
|
|
|
|
Value::String(string) => results.push(string.to_string()),
|
|
|
|
Value::Object(object) => {
|
|
|
|
if let Some(string) = object["id"].as_str() {
|
|
|
|
results.push(string.to_string());
|
|
|
|
} else {
|
|
|
|
// id property is missing
|
|
|
|
return Err(ConversionError);
|
|
|
|
};
|
|
|
|
},
|
|
|
|
// Unexpected array item type
|
|
|
|
_ => return Err(ConversionError),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
results
|
2021-11-18 23:24:13 +00:00
|
|
|
},
|
2021-12-24 15:13:25 +00:00
|
|
|
// Unexpected value type
|
|
|
|
_ => return Err(ConversionError),
|
2021-11-18 23:24:13 +00:00
|
|
|
};
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2022-04-13 22:30:41 +00:00
|
|
|
/// Transforms arbitrary property value into array of structs
|
|
|
|
fn parse_property_value<T: DeserializeOwned>(value: &Value) -> Result<Vec<T>, ConversionError> {
|
|
|
|
let objects = match value {
|
|
|
|
Value::Array(array) => array.to_vec(),
|
|
|
|
Value::Object(_) => vec![value.clone()],
|
|
|
|
// Unexpected value type
|
|
|
|
_ => return Err(ConversionError),
|
|
|
|
};
|
|
|
|
let mut items = vec![];
|
|
|
|
for object in objects {
|
|
|
|
let item: T = serde_json::from_value(object)
|
|
|
|
.map_err(|_| ConversionError)?;
|
|
|
|
items.push(item);
|
|
|
|
};
|
|
|
|
Ok(items)
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:45:36 +00:00
|
|
|
/// Parses object json value and returns its ID as string
|
|
|
|
fn get_object_id(object: Value) -> Result<String, ValidationError> {
|
|
|
|
let object_id = match object.as_str() {
|
|
|
|
Some(object_id) => object_id.to_owned(),
|
|
|
|
None => {
|
|
|
|
let object: Object = serde_json::from_value(object)
|
|
|
|
.map_err(|_| ValidationError("invalid object"))?;
|
|
|
|
object.id
|
|
|
|
},
|
|
|
|
};
|
|
|
|
Ok(object_id)
|
|
|
|
}
|
|
|
|
|
2022-02-08 21:33:05 +00:00
|
|
|
const CONTENT_MAX_SIZE: usize = 100000;
|
|
|
|
|
|
|
|
fn clean_note_content(content: &str) -> Result<String, ValidationError> {
|
|
|
|
if content.len() > CONTENT_MAX_SIZE {
|
|
|
|
return Err(ValidationError("content is too long"));
|
|
|
|
};
|
|
|
|
let content_safe = clean_html(content);
|
|
|
|
Ok(content_safe)
|
|
|
|
}
|
|
|
|
|
2022-04-13 19:16:34 +00:00
|
|
|
pub async fn import_post(
|
2021-11-07 16:48:47 +00:00
|
|
|
config: &Config,
|
|
|
|
db_client: &mut impl GenericClient,
|
2021-12-05 22:10:35 +00:00
|
|
|
object_id: String,
|
|
|
|
object_received: Option<Object>,
|
2022-04-13 19:16:34 +00:00
|
|
|
) -> Result<Post, ImportError> {
|
2021-11-18 14:37:48 +00:00
|
|
|
let instance = config.instance();
|
2021-12-05 22:10:35 +00:00
|
|
|
let mut maybe_object_id_to_fetch = Some(object_id);
|
|
|
|
let mut maybe_object = object_received;
|
|
|
|
let mut objects = vec![];
|
2021-12-20 01:00:35 +00:00
|
|
|
let mut redirects: HashMap<String, String> = HashMap::new();
|
2021-11-07 21:07:38 +00:00
|
|
|
let mut posts = vec![];
|
|
|
|
|
2021-11-07 16:48:47 +00:00
|
|
|
// Fetch ancestors by going through inReplyTo references
|
|
|
|
// TODO: fetch replies too
|
2021-11-13 17:37:31 +00:00
|
|
|
#[allow(clippy::while_let_loop)]
|
2021-11-07 16:48:47 +00:00
|
|
|
loop {
|
2021-12-05 22:10:35 +00:00
|
|
|
let object_id = match maybe_object_id_to_fetch {
|
|
|
|
Some(object_id) => {
|
|
|
|
if parse_object_id(&instance.url(), &object_id).is_ok() {
|
|
|
|
// Object is a local post
|
|
|
|
assert!(objects.len() > 0);
|
2021-11-07 16:48:47 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-12-05 22:10:35 +00:00
|
|
|
match get_post_by_object_id(db_client, &object_id).await {
|
|
|
|
Ok(post) => {
|
|
|
|
// Object already fetched
|
|
|
|
if objects.len() == 0 {
|
|
|
|
// Return post corresponding to initial object ID
|
|
|
|
return Ok(post);
|
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
break;
|
|
|
|
},
|
|
|
|
Err(DatabaseError::NotFound(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2021-12-05 22:10:35 +00:00
|
|
|
object_id
|
2021-11-07 16:48:47 +00:00
|
|
|
},
|
|
|
|
None => {
|
2021-12-05 22:10:35 +00:00
|
|
|
// No object to fetch
|
2021-11-07 16:48:47 +00:00
|
|
|
break;
|
|
|
|
},
|
|
|
|
};
|
2021-12-05 22:10:35 +00:00
|
|
|
let object = match maybe_object {
|
|
|
|
Some(object) => object,
|
|
|
|
None => {
|
|
|
|
let object = fetch_object(&instance, &object_id).await
|
2021-12-20 23:07:02 +00:00
|
|
|
.map_err(|err| {
|
|
|
|
log::warn!("{}", err);
|
|
|
|
ValidationError("failed to fetch object")
|
|
|
|
})?;
|
2021-12-05 22:10:35 +00:00
|
|
|
log::info!("fetched object {}", object.id);
|
|
|
|
object
|
|
|
|
},
|
|
|
|
};
|
|
|
|
if object.id != object_id {
|
|
|
|
// ID of fetched object doesn't match requested ID
|
2021-12-20 01:00:35 +00:00
|
|
|
// Add IDs to the map of redirects
|
|
|
|
redirects.insert(object_id, object.id.clone());
|
2021-12-05 22:10:35 +00:00
|
|
|
maybe_object_id_to_fetch = Some(object.id.clone());
|
2021-12-20 01:00:35 +00:00
|
|
|
// Don't re-fetch object on the next iteration
|
2021-12-05 22:10:35 +00:00
|
|
|
maybe_object = Some(object);
|
|
|
|
} else {
|
|
|
|
maybe_object_id_to_fetch = object.in_reply_to.clone();
|
|
|
|
maybe_object = None;
|
|
|
|
objects.push(object);
|
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
}
|
2021-12-05 22:10:35 +00:00
|
|
|
let initial_object_id = objects[0].id.clone();
|
2021-11-07 21:07:38 +00:00
|
|
|
|
2021-11-07 16:48:47 +00:00
|
|
|
// Objects are ordered according to their place in reply tree,
|
|
|
|
// starting with the root
|
|
|
|
objects.reverse();
|
|
|
|
for object in objects {
|
2022-04-13 19:00:27 +00:00
|
|
|
if object.object_type != NOTE {
|
|
|
|
// Could be Page (in Lemmy) or some other type
|
|
|
|
log::warn!("processing object of type {}", object.object_type);
|
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
let attributed_to = object.attributed_to
|
|
|
|
.ok_or(ValidationError("unattributed note"))?;
|
2021-12-24 15:13:25 +00:00
|
|
|
let author_id = parse_array(&attributed_to)
|
|
|
|
.map_err(|_| ValidationError("invalid attributedTo property"))?
|
|
|
|
.get(0)
|
|
|
|
.ok_or(ValidationError("invalid attributedTo property"))?
|
|
|
|
.to_string();
|
2021-12-28 18:34:13 +00:00
|
|
|
let author = get_or_import_profile_by_actor_id(
|
2021-11-07 16:48:47 +00:00
|
|
|
db_client,
|
2021-11-18 14:37:48 +00:00
|
|
|
&instance,
|
2021-11-07 16:48:47 +00:00
|
|
|
&config.media_dir(),
|
2021-12-28 18:34:13 +00:00
|
|
|
&author_id,
|
2021-11-07 16:48:47 +00:00
|
|
|
).await?;
|
|
|
|
let content = object.content
|
|
|
|
.ok_or(ValidationError("no content"))?;
|
2022-02-08 21:33:05 +00:00
|
|
|
let content_cleaned = clean_note_content(&content)?;
|
2021-11-07 16:48:47 +00:00
|
|
|
let mut attachments: Vec<Uuid> = Vec::new();
|
2022-04-13 22:30:41 +00:00
|
|
|
if let Some(value) = object.attachment {
|
|
|
|
let list: Vec<Attachment> = parse_property_value(&value)
|
|
|
|
.map_err(|_| ValidationError("invalid attachment property"))?;
|
2021-12-29 13:57:57 +00:00
|
|
|
let mut downloaded = vec![];
|
2021-11-07 16:48:47 +00:00
|
|
|
let output_dir = config.media_dir();
|
|
|
|
for attachment in list {
|
2022-01-17 11:28:44 +00:00
|
|
|
if attachment.attachment_type != DOCUMENT &&
|
|
|
|
attachment.attachment_type != IMAGE
|
|
|
|
{
|
2022-01-08 21:16:12 +00:00
|
|
|
log::warn!(
|
|
|
|
"skipping attachment of type {}",
|
|
|
|
attachment.attachment_type,
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let attachment_url = attachment.url
|
|
|
|
.ok_or(ValidationError("attachment URL is missing"))?;
|
|
|
|
let (file_name, media_type) = fetch_attachment(&attachment_url, &output_dir).await
|
2021-11-07 16:48:47 +00:00
|
|
|
.map_err(|_| ValidationError("failed to fetch attachment"))?;
|
2022-01-08 21:16:12 +00:00
|
|
|
log::info!("downloaded attachment {}", attachment_url);
|
2021-12-29 13:57:57 +00:00
|
|
|
downloaded.push((
|
|
|
|
file_name,
|
|
|
|
attachment.media_type.or(media_type),
|
|
|
|
));
|
2022-04-13 22:30:41 +00:00
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
for (file_name, media_type) in downloaded {
|
|
|
|
let db_attachment = create_attachment(
|
|
|
|
db_client,
|
|
|
|
&author.id,
|
|
|
|
file_name,
|
2021-12-29 13:57:57 +00:00
|
|
|
media_type,
|
2021-11-07 16:48:47 +00:00
|
|
|
).await?;
|
|
|
|
attachments.push(db_attachment.id);
|
2022-04-13 22:30:41 +00:00
|
|
|
};
|
|
|
|
};
|
2021-11-11 21:51:47 +00:00
|
|
|
let mut mentions: Vec<Uuid> = Vec::new();
|
2021-12-07 23:28:58 +00:00
|
|
|
let mut tags = vec![];
|
2021-11-11 21:51:47 +00:00
|
|
|
if let Some(list) = object.tag {
|
|
|
|
for tag in list {
|
2021-12-07 23:28:58 +00:00
|
|
|
if tag.tag_type == HASHTAG {
|
2022-04-27 09:53:52 +00:00
|
|
|
if let Some(tag_name) = tag.name {
|
|
|
|
// Ignore invalid tags
|
|
|
|
if let Ok(tag_name) = normalize_tag(&tag_name) {
|
|
|
|
tags.push(tag_name);
|
|
|
|
};
|
2021-12-07 23:28:58 +00:00
|
|
|
};
|
|
|
|
} else if tag.tag_type == MENTION {
|
2022-04-20 11:20:59 +00:00
|
|
|
// Try to find profile by actor ID.
|
2022-01-14 00:14:52 +00:00
|
|
|
if let Some(href) = tag.href {
|
2022-04-20 11:20:59 +00:00
|
|
|
if let Ok(username) = parse_actor_id(&config.instance_url(), &href) {
|
|
|
|
let user = get_user_by_name(db_client, &username).await?;
|
|
|
|
if !mentions.contains(&user.id) {
|
|
|
|
mentions.push(user.id);
|
|
|
|
};
|
|
|
|
continue;
|
2022-01-21 11:00:32 +00:00
|
|
|
};
|
2022-04-20 11:20:59 +00:00
|
|
|
// WARNING: `href` attribute is usually actor ID
|
|
|
|
// but also can be actor URL (profile link).
|
|
|
|
// This may lead to failed import due to
|
|
|
|
// unique constraint violation on DB insert.
|
2022-01-14 00:14:52 +00:00
|
|
|
match get_or_import_profile_by_actor_id(
|
|
|
|
db_client,
|
|
|
|
&instance,
|
|
|
|
&config.media_dir(),
|
|
|
|
&href,
|
|
|
|
).await {
|
|
|
|
Ok(profile) => {
|
|
|
|
if !mentions.contains(&profile.id) {
|
|
|
|
mentions.push(profile.id);
|
|
|
|
};
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Err(error) => {
|
|
|
|
log::warn!("failed to find mentioned profile {}: {}", href, error);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
2022-04-20 11:20:59 +00:00
|
|
|
// Try to find profile by actor address
|
2022-04-27 09:53:52 +00:00
|
|
|
let tag_name = match tag.name {
|
|
|
|
Some(name) => name,
|
|
|
|
None => {
|
|
|
|
log::warn!("failed to parse mention");
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
};
|
2021-12-19 22:15:14 +00:00
|
|
|
if let Ok(actor_address) = mention_to_address(
|
|
|
|
&instance.host(),
|
2022-04-27 09:53:52 +00:00
|
|
|
&tag_name,
|
2021-12-19 22:15:14 +00:00
|
|
|
) {
|
2021-12-19 21:54:25 +00:00
|
|
|
let profile = match get_profile_by_acct(
|
2021-12-19 22:15:14 +00:00
|
|
|
db_client,
|
|
|
|
&actor_address.acct(),
|
2021-12-19 21:54:25 +00:00
|
|
|
).await {
|
|
|
|
Ok(profile) => profile,
|
|
|
|
Err(DatabaseError::NotFound(_)) => {
|
2021-12-25 16:17:27 +00:00
|
|
|
match import_profile_by_actor_address(
|
2021-12-22 23:27:32 +00:00
|
|
|
db_client,
|
2021-12-19 21:54:25 +00:00
|
|
|
&config.instance(),
|
|
|
|
&config.media_dir(),
|
2021-12-22 23:27:32 +00:00
|
|
|
&actor_address,
|
2021-12-25 16:17:27 +00:00
|
|
|
).await {
|
|
|
|
Ok(profile) => profile,
|
|
|
|
Err(ImportError::FetchError(error)) => {
|
|
|
|
// Ignore mention if fetcher fails
|
|
|
|
log::warn!("{}", error);
|
|
|
|
continue;
|
|
|
|
},
|
2021-12-27 19:20:21 +00:00
|
|
|
Err(other_error) => {
|
2022-04-13 22:30:41 +00:00
|
|
|
return Err(other_error);
|
2021-12-27 19:20:21 +00:00
|
|
|
},
|
2021-12-25 16:17:27 +00:00
|
|
|
}
|
2021-12-19 21:54:25 +00:00
|
|
|
},
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2021-12-08 00:03:12 +00:00
|
|
|
if !mentions.contains(&profile.id) {
|
|
|
|
mentions.push(profile.id);
|
|
|
|
};
|
2022-01-14 00:14:52 +00:00
|
|
|
} else {
|
2022-04-27 09:53:52 +00:00
|
|
|
log::warn!("failed to parse mention {}", tag_name);
|
2021-12-08 00:03:12 +00:00
|
|
|
};
|
2021-11-11 21:51:47 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
let in_reply_to_id = match object.in_reply_to {
|
|
|
|
Some(object_id) => {
|
2021-11-18 14:37:48 +00:00
|
|
|
match parse_object_id(&instance.url(), &object_id) {
|
2021-11-07 16:48:47 +00:00
|
|
|
Ok(post_id) => {
|
|
|
|
// Local post
|
|
|
|
let post = get_post_by_id(db_client, &post_id).await?;
|
|
|
|
Some(post.id)
|
|
|
|
},
|
|
|
|
Err(_) => {
|
2021-12-20 01:00:35 +00:00
|
|
|
let note_id = redirects.get(&object_id)
|
|
|
|
.unwrap_or(&object_id);
|
|
|
|
let post = get_post_by_object_id(db_client, note_id).await?;
|
2021-11-07 16:48:47 +00:00
|
|
|
Some(post.id)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => None,
|
|
|
|
};
|
2022-01-08 19:22:00 +00:00
|
|
|
let primary_audience = match object.to {
|
2021-11-18 23:24:13 +00:00
|
|
|
Some(value) => {
|
2022-01-08 19:22:00 +00:00
|
|
|
parse_array(&value)
|
|
|
|
.map_err(|_| ValidationError("invalid 'to' property value"))?
|
|
|
|
},
|
|
|
|
None => vec![],
|
|
|
|
};
|
|
|
|
let secondary_audience = match object.cc {
|
|
|
|
Some(value) => {
|
|
|
|
parse_array(&value)
|
|
|
|
.map_err(|_| ValidationError("invalid 'to' property value"))?
|
2021-11-18 23:24:13 +00:00
|
|
|
},
|
2022-01-08 19:22:00 +00:00
|
|
|
None => vec![],
|
|
|
|
};
|
2022-02-13 18:55:37 +00:00
|
|
|
let visibility = get_note_visibility(
|
|
|
|
&author,
|
|
|
|
primary_audience,
|
|
|
|
secondary_audience,
|
|
|
|
);
|
2022-02-13 17:55:35 +00:00
|
|
|
if visibility != Visibility::Public {
|
|
|
|
log::warn!(
|
|
|
|
"processing note with visibility {:?} attributed to {}",
|
|
|
|
visibility,
|
|
|
|
author.username,
|
|
|
|
);
|
|
|
|
};
|
2021-11-07 16:48:47 +00:00
|
|
|
let post_data = PostCreateData {
|
2022-02-08 21:33:05 +00:00
|
|
|
content: content_cleaned,
|
2021-11-07 16:48:47 +00:00
|
|
|
in_reply_to_id,
|
2021-11-24 16:39:30 +00:00
|
|
|
repost_of_id: None,
|
2021-11-18 23:24:13 +00:00
|
|
|
visibility,
|
2021-11-07 16:48:47 +00:00
|
|
|
attachments: attachments,
|
2021-11-11 21:51:47 +00:00
|
|
|
mentions: mentions,
|
2021-12-07 23:28:58 +00:00
|
|
|
tags: tags,
|
2021-11-07 16:48:47 +00:00
|
|
|
object_id: Some(object.id),
|
|
|
|
created_at: object.published,
|
|
|
|
};
|
2021-11-07 21:07:38 +00:00
|
|
|
let post = create_post(db_client, &author.id, post_data).await?;
|
|
|
|
posts.push(post);
|
2021-11-07 16:48:47 +00:00
|
|
|
}
|
2021-11-07 21:07:38 +00:00
|
|
|
|
|
|
|
let initial_post = posts.into_iter()
|
|
|
|
.find(|post| post.object_id.as_ref() == Some(&initial_object_id))
|
|
|
|
.unwrap();
|
|
|
|
Ok(initial_post)
|
2021-11-07 16:48:47 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 19:33:29 +00:00
|
|
|
fn require_actor_signature(actor_id: &str, signer_id: &str)
|
|
|
|
-> Result<(), HttpError>
|
|
|
|
{
|
|
|
|
if actor_id != signer_id {
|
2022-01-04 14:42:03 +00:00
|
|
|
log::warn!(
|
|
|
|
"request signer {} does not match actor {}",
|
|
|
|
signer_id,
|
|
|
|
actor_id,
|
|
|
|
);
|
2022-01-03 19:33:29 +00:00
|
|
|
return Err(HttpError::AuthError("actor and request signer do not match"));
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn receive_activity(
|
|
|
|
config: &Config,
|
2022-02-23 23:07:51 +00:00
|
|
|
db_client: &mut impl GenericClient,
|
|
|
|
request: &HttpRequest,
|
2021-12-21 00:14:12 +00:00
|
|
|
activity_raw: &Value,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<(), HttpError> {
|
2022-02-23 23:07:51 +00:00
|
|
|
let signer = verify_http_signature(config, db_client, request).await.map_err(|err| {
|
|
|
|
log::warn!("invalid signature: {}", err);
|
|
|
|
HttpError::AuthError("invalid signature")
|
|
|
|
})?;
|
|
|
|
let signer_id = signer.actor_id(&config.instance_url());
|
|
|
|
log::debug!("activity signed by {}", signer_id);
|
2022-02-23 23:31:56 +00:00
|
|
|
if config.blocked_instances.iter().any(|instance| signer.acct.contains(instance)) {
|
|
|
|
return Err(HttpError::ValidationError("instance is blocked".into()));
|
|
|
|
};
|
2022-02-23 23:07:51 +00:00
|
|
|
|
2021-12-21 00:14:12 +00:00
|
|
|
let activity: Activity = serde_json::from_value(activity_raw.clone())
|
2021-04-09 00:22:17 +00:00
|
|
|
.map_err(|_| ValidationError("invalid activity"))?;
|
|
|
|
let activity_type = activity.activity_type;
|
2021-12-17 23:47:46 +00:00
|
|
|
let maybe_object_type = activity.object.get("type")
|
2021-04-09 00:22:17 +00:00
|
|
|
.and_then(|val| val.as_str())
|
2021-12-17 23:47:46 +00:00
|
|
|
.unwrap_or("Unknown");
|
|
|
|
let object_type = match (activity_type.as_str(), maybe_object_type) {
|
2021-04-09 00:22:17 +00:00
|
|
|
(ACCEPT, FOLLOW) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2022-01-03 23:19:52 +00:00
|
|
|
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
|
|
|
let follow_request_id = parse_object_id(&config.instance_url(), &object_id)?;
|
2022-01-03 23:19:52 +00:00
|
|
|
let follow_request = get_follow_request_by_id(db_client, &follow_request_id).await?;
|
|
|
|
if follow_request.target_id != actor_profile.id {
|
|
|
|
return Err(HttpError::ValidationError("actor is not a target".into()));
|
|
|
|
};
|
2021-09-20 21:06:48 +00:00
|
|
|
follow_request_accepted(db_client, &follow_request_id).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
FOLLOW
|
2021-09-20 21:06:48 +00:00
|
|
|
},
|
|
|
|
(REJECT, FOLLOW) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2022-01-03 23:19:52 +00:00
|
|
|
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
|
|
|
let follow_request_id = parse_object_id(&config.instance_url(), &object_id)?;
|
2022-01-03 23:19:52 +00:00
|
|
|
let follow_request = get_follow_request_by_id(db_client, &follow_request_id).await?;
|
|
|
|
if follow_request.target_id != actor_profile.id {
|
|
|
|
return Err(HttpError::ValidationError("actor is not a target".into()));
|
|
|
|
};
|
2021-09-20 21:06:48 +00:00
|
|
|
follow_request_rejected(db_client, &follow_request_id).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
FOLLOW
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
|
|
|
(CREATE, NOTE) => {
|
|
|
|
let object: Object = serde_json::from_value(activity.object)
|
|
|
|
.map_err(|_| ValidationError("invalid object"))?;
|
2022-01-03 19:33:29 +00:00
|
|
|
let object_id = object.id.clone();
|
|
|
|
let object_received = if activity.actor == signer_id {
|
|
|
|
Some(object)
|
|
|
|
} else {
|
2022-01-03 23:38:11 +00:00
|
|
|
// Fetch forwarded note, don't trust the sender
|
2022-01-03 19:33:29 +00:00
|
|
|
None
|
|
|
|
};
|
2022-04-13 19:16:34 +00:00
|
|
|
import_post(config, db_client, object_id, object_received).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
NOTE
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
2021-11-25 21:28:06 +00:00
|
|
|
(ANNOUNCE, _) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-12-11 21:21:52 +00:00
|
|
|
let repost_object_id = activity.id;
|
|
|
|
match get_post_by_object_id(db_client, &repost_object_id).await {
|
|
|
|
Ok(_) => return Ok(()), // Ignore if repost already exists
|
|
|
|
Err(DatabaseError::NotFound(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2021-12-28 18:34:13 +00:00
|
|
|
let author = get_or_import_profile_by_actor_id(
|
2021-11-25 21:28:06 +00:00
|
|
|
db_client,
|
|
|
|
&config.instance(),
|
|
|
|
&config.media_dir(),
|
2021-12-28 18:34:13 +00:00
|
|
|
&activity.actor,
|
2021-11-25 21:28:06 +00:00
|
|
|
).await?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
2021-11-25 21:28:06 +00:00
|
|
|
let post_id = match parse_object_id(&config.instance_url(), &object_id) {
|
|
|
|
Ok(post_id) => post_id,
|
|
|
|
Err(_) => {
|
2021-12-06 15:28:41 +00:00
|
|
|
// Try to get remote post
|
2022-04-13 19:16:34 +00:00
|
|
|
let post = import_post(config, db_client, object_id, None).await?;
|
2021-11-25 21:28:06 +00:00
|
|
|
post.id
|
|
|
|
},
|
|
|
|
};
|
|
|
|
let repost_data = PostCreateData {
|
|
|
|
repost_of_id: Some(post_id),
|
2021-12-11 21:21:52 +00:00
|
|
|
object_id: Some(repost_object_id),
|
2021-11-25 21:28:06 +00:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
create_post(db_client, &author.id, repost_data).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
NOTE
|
2021-11-25 21:28:06 +00:00
|
|
|
},
|
2021-11-19 00:05:39 +00:00
|
|
|
(DELETE, _) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
2021-12-28 01:28:28 +00:00
|
|
|
if object_id == activity.actor {
|
|
|
|
log::info!("received deletion request for {}", object_id);
|
|
|
|
// Ignore Delete(Person)
|
|
|
|
return Ok(());
|
|
|
|
};
|
2021-12-30 14:45:14 +00:00
|
|
|
let post = match get_post_by_object_id(db_client, &object_id).await {
|
|
|
|
Ok(post) => post,
|
|
|
|
// Ignore Delete(Note) if post is not found
|
|
|
|
Err(DatabaseError::NotFound(_)) => return Ok(()),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2022-01-03 23:38:11 +00:00
|
|
|
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
|
|
|
|
if post.author.id != actor_profile.id {
|
|
|
|
return Err(HttpError::ValidationError("actor is not an author".into()));
|
|
|
|
};
|
2021-11-12 14:51:03 +00:00
|
|
|
let deletion_queue = delete_post(db_client, &post.id).await?;
|
|
|
|
let config = config.clone();
|
|
|
|
actix_rt::spawn(async move {
|
|
|
|
deletion_queue.process(&config).await;
|
|
|
|
});
|
2021-12-17 23:47:46 +00:00
|
|
|
NOTE
|
2021-11-12 14:51:03 +00:00
|
|
|
},
|
2021-11-30 20:06:13 +00:00
|
|
|
(LIKE, _) | (EMOJI_REACT, _) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-12-28 18:34:13 +00:00
|
|
|
let author = get_or_import_profile_by_actor_id(
|
2021-10-29 19:21:26 +00:00
|
|
|
db_client,
|
2021-11-15 19:25:43 +00:00
|
|
|
&config.instance(),
|
2021-10-29 19:21:26 +00:00
|
|
|
&config.media_dir(),
|
2021-12-28 18:34:13 +00:00
|
|
|
&activity.actor,
|
2021-10-29 19:21:26 +00:00
|
|
|
).await?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
2021-11-14 22:57:18 +00:00
|
|
|
let post_id = match parse_object_id(&config.instance_url(), &object_id) {
|
|
|
|
Ok(post_id) => post_id,
|
|
|
|
Err(_) => {
|
|
|
|
let post = match get_post_by_object_id(db_client, &object_id).await {
|
|
|
|
Ok(post) => post,
|
|
|
|
// Ignore like if post is not found locally
|
|
|
|
Err(DatabaseError::NotFound(_)) => return Ok(()),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
post.id
|
|
|
|
},
|
|
|
|
};
|
2021-12-25 21:44:08 +00:00
|
|
|
match create_reaction(
|
2021-12-14 22:59:15 +00:00
|
|
|
db_client,
|
|
|
|
&author.id,
|
|
|
|
&post_id,
|
|
|
|
Some(&activity.id),
|
2021-12-25 21:44:08 +00:00
|
|
|
).await {
|
|
|
|
Ok(_) => (),
|
|
|
|
// Ignore activity if reaction is already saved
|
|
|
|
Err(DatabaseError::AlreadyExists(_)) => return Ok(()),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2021-12-17 23:47:46 +00:00
|
|
|
NOTE
|
2021-10-29 19:21:26 +00:00
|
|
|
},
|
2021-04-09 00:22:17 +00:00
|
|
|
(FOLLOW, _) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-12-28 18:34:13 +00:00
|
|
|
let source_profile = get_or_import_profile_by_actor_id(
|
2021-10-09 22:58:21 +00:00
|
|
|
db_client,
|
2021-11-15 19:25:43 +00:00
|
|
|
&config.instance(),
|
2021-10-09 22:58:21 +00:00
|
|
|
&config.media_dir(),
|
2021-12-28 18:34:13 +00:00
|
|
|
&activity.actor,
|
2021-10-09 22:58:21 +00:00
|
|
|
).await?;
|
2021-12-31 15:29:44 +00:00
|
|
|
let source_actor = source_profile.actor_json
|
2021-10-31 20:46:29 +00:00
|
|
|
.ok_or(HttpError::InternalError)?;
|
2021-11-29 20:45:36 +00:00
|
|
|
let target_actor_id = get_object_id(activity.object)?;
|
2021-09-20 21:45:33 +00:00
|
|
|
let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?;
|
2022-01-02 23:50:37 +00:00
|
|
|
let target_user = get_user_by_name(db_client, &target_username).await?;
|
|
|
|
match follow(db_client, &source_profile.id, &target_user.profile.id).await {
|
|
|
|
Ok(_) => (),
|
|
|
|
// Proceed even if relationship already exists
|
|
|
|
Err(DatabaseError::AlreadyExists(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Send activity
|
2021-10-09 12:53:53 +00:00
|
|
|
let new_activity = create_activity_accept_follow(
|
|
|
|
&config.instance_url(),
|
2022-01-02 23:50:37 +00:00
|
|
|
&target_user.profile,
|
2021-10-09 12:53:53 +00:00
|
|
|
&activity.id,
|
2021-11-18 21:28:50 +00:00
|
|
|
&source_actor.id,
|
2021-10-09 12:53:53 +00:00
|
|
|
);
|
2021-04-09 00:22:17 +00:00
|
|
|
let recipients = vec![source_actor];
|
2021-11-13 17:37:31 +00:00
|
|
|
deliver_activity(config, &target_user, new_activity, recipients);
|
2021-12-17 23:47:46 +00:00
|
|
|
PERSON
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
|
|
|
(UNDO, FOLLOW) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let object: Object = serde_json::from_value(activity.object)
|
|
|
|
.map_err(|_| ValidationError("invalid object"))?;
|
|
|
|
let source_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
|
|
|
|
let target_actor_id = object.object
|
|
|
|
.ok_or(ValidationError("invalid object"))?;
|
2021-09-20 21:45:33 +00:00
|
|
|
let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let target_profile = get_profile_by_acct(db_client, &target_username).await?;
|
|
|
|
unfollow(db_client, &source_profile.id, &target_profile.id).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
FOLLOW
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
2021-12-15 01:00:42 +00:00
|
|
|
(UNDO, _) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2022-01-03 23:38:11 +00:00
|
|
|
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
|
2021-12-15 01:00:42 +00:00
|
|
|
let object_id = get_object_id(activity.object)?;
|
2021-12-16 17:32:49 +00:00
|
|
|
match get_reaction_by_activity_id(db_client, &object_id).await {
|
|
|
|
Ok(reaction) => {
|
|
|
|
// Undo(Like)
|
2022-01-03 23:38:11 +00:00
|
|
|
if reaction.author_id != actor_profile.id {
|
|
|
|
return Err(HttpError::ValidationError("actor is not an author".into()));
|
|
|
|
};
|
2021-12-26 14:10:11 +00:00
|
|
|
delete_reaction(
|
2021-12-16 17:32:49 +00:00
|
|
|
db_client,
|
|
|
|
&reaction.author_id,
|
|
|
|
&reaction.post_id,
|
2021-12-26 14:10:11 +00:00
|
|
|
).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
LIKE
|
2021-12-16 17:32:49 +00:00
|
|
|
},
|
|
|
|
Err(DatabaseError::NotFound(_)) => {
|
|
|
|
// Undo(Announce)
|
2021-12-26 14:10:11 +00:00
|
|
|
let post = match get_post_by_object_id(db_client, &object_id).await {
|
|
|
|
Ok(post) => post,
|
|
|
|
// Ignore undo if neither reaction nor repost is found
|
|
|
|
Err(DatabaseError::NotFound(_)) => return Ok(()),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2022-01-03 23:38:11 +00:00
|
|
|
if post.author.id != actor_profile.id {
|
|
|
|
return Err(HttpError::ValidationError("actor is not an author".into()));
|
|
|
|
};
|
2021-12-16 17:32:49 +00:00
|
|
|
match post.repost_of_id {
|
2021-12-26 14:10:11 +00:00
|
|
|
// Ignore returned data because reposts don't have attached files
|
2021-12-16 17:32:49 +00:00
|
|
|
Some(_) => delete_post(db_client, &post.id).await?,
|
2021-12-26 14:10:11 +00:00
|
|
|
// Can't undo regular post
|
|
|
|
None => return Err(HttpError::ValidationError("object is not a repost".into())),
|
2021-12-16 17:32:49 +00:00
|
|
|
};
|
2021-12-17 23:47:46 +00:00
|
|
|
ANNOUNCE
|
2021-12-16 17:32:49 +00:00
|
|
|
},
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
2021-12-17 23:47:46 +00:00
|
|
|
}
|
2021-12-15 01:00:42 +00:00
|
|
|
},
|
2021-04-09 00:22:17 +00:00
|
|
|
(UPDATE, PERSON) => {
|
2022-02-23 23:07:51 +00:00
|
|
|
require_actor_signature(&activity.actor, &signer_id)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let actor: Actor = serde_json::from_value(activity.object)
|
|
|
|
.map_err(|_| ValidationError("invalid actor data"))?;
|
2022-01-03 23:45:18 +00:00
|
|
|
if actor.id != activity.actor {
|
|
|
|
return Err(HttpError::ValidationError("actor ID mismatch".into()));
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
|
|
|
|
let (avatar, banner) = fetch_avatar_and_banner(&actor, &config.media_dir()).await
|
|
|
|
.map_err(|_| ValidationError("failed to fetch image"))?;
|
2022-04-16 18:48:00 +00:00
|
|
|
let (identity_proofs, extra_fields) = actor.parse_attachments();
|
2021-12-31 15:29:44 +00:00
|
|
|
let actor_old = profile.actor_json.unwrap();
|
2021-12-31 12:01:08 +00:00
|
|
|
if actor_old.id != actor.id {
|
|
|
|
log::warn!(
|
|
|
|
"actor ID changed from {} to {}",
|
|
|
|
actor_old.id,
|
|
|
|
actor.id,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
if actor_old.public_key.public_key_pem != actor.public_key.public_key_pem {
|
|
|
|
log::warn!(
|
|
|
|
"actor public key changed from {} to {}",
|
|
|
|
actor_old.public_key.public_key_pem,
|
|
|
|
actor.public_key.public_key_pem,
|
|
|
|
);
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
let mut profile_data = ProfileUpdateData {
|
2022-04-21 22:52:28 +00:00
|
|
|
display_name: actor.name.clone(),
|
2021-04-09 00:22:17 +00:00
|
|
|
bio: actor.summary.clone(),
|
2022-04-21 22:52:28 +00:00
|
|
|
bio_source: actor.summary.clone(),
|
2021-04-09 00:22:17 +00:00
|
|
|
avatar,
|
|
|
|
banner,
|
2022-04-16 18:48:00 +00:00
|
|
|
identity_proofs,
|
2021-09-17 12:36:24 +00:00
|
|
|
extra_fields,
|
2022-04-21 22:52:28 +00:00
|
|
|
actor_json: Some(actor),
|
2021-04-09 00:22:17 +00:00
|
|
|
};
|
|
|
|
profile_data.clean()?;
|
|
|
|
update_profile(db_client, &profile.id, profile_data).await?;
|
2021-12-17 23:47:46 +00:00
|
|
|
PERSON
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
return Err(HttpError::ValidationError("activity type is not supported".into()));
|
|
|
|
},
|
|
|
|
};
|
|
|
|
log::info!(
|
|
|
|
"processed {}({}) from {}",
|
|
|
|
activity_type,
|
|
|
|
object_type,
|
|
|
|
activity.actor,
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-11-18 23:24:13 +00:00
|
|
|
use serde_json::json;
|
2021-12-01 23:26:59 +00:00
|
|
|
use crate::utils::id::new_uuid;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::*;
|
|
|
|
|
2021-09-20 21:45:33 +00:00
|
|
|
const INSTANCE_URL: &str = "https://example.org";
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_actor_id() {
|
2021-09-20 21:45:33 +00:00
|
|
|
let username = parse_actor_id(INSTANCE_URL, "https://example.org/users/test").unwrap();
|
2021-04-09 00:22:17 +00:00
|
|
|
assert_eq!(username, "test".to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_actor_id_wrong_path() {
|
2021-09-20 21:45:33 +00:00
|
|
|
let error = parse_actor_id(INSTANCE_URL, "https://example.org/user/test").unwrap_err();
|
2021-04-09 00:22:17 +00:00
|
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_actor_id_invalid_username() {
|
2021-09-20 21:45:33 +00:00
|
|
|
let error = parse_actor_id(INSTANCE_URL, "https://example.org/users/tes-t").unwrap_err();
|
|
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_actor_id_invalid_instance_url() {
|
|
|
|
let error = parse_actor_id(INSTANCE_URL, "https://example.gov/users/test").unwrap_err();
|
2021-04-09 00:22:17 +00:00
|
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_object_id() {
|
2021-12-01 23:26:59 +00:00
|
|
|
let expected_uuid = new_uuid();
|
2021-04-09 00:22:17 +00:00
|
|
|
let object_id = format!(
|
|
|
|
"https://example.org/objects/{}",
|
|
|
|
expected_uuid,
|
|
|
|
);
|
2021-11-29 20:45:36 +00:00
|
|
|
let internal_object_id = parse_object_id(INSTANCE_URL, &object_id).unwrap();
|
|
|
|
assert_eq!(internal_object_id, expected_uuid);
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_object_id_invalid_uuid() {
|
2021-09-20 21:45:33 +00:00
|
|
|
let object_id = "https://example.org/objects/1234";
|
|
|
|
let error = parse_object_id(INSTANCE_URL, object_id).unwrap_err();
|
2021-04-09 00:22:17 +00:00
|
|
|
assert_eq!(error.to_string(), "invalid object ID");
|
|
|
|
}
|
2021-11-18 23:24:13 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_string() {
|
|
|
|
let value = json!("test");
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test".to_string()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_array() {
|
|
|
|
let value = json!(["test1", "test2"]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test1".to_string(), "test2".to_string()],
|
|
|
|
);
|
|
|
|
}
|
2021-11-29 20:45:36 +00:00
|
|
|
|
2021-12-24 15:13:25 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_array_of_objects() {
|
|
|
|
let value = json!([{"id": "test1"}, {"id": "test2"}]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test1".to_string(), "test2".to_string()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:45:36 +00:00
|
|
|
#[test]
|
|
|
|
fn test_get_object_id_from_string() {
|
|
|
|
let value = json!("test_id");
|
|
|
|
assert_eq!(get_object_id(value).unwrap(), "test_id");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_object_id_from_object() {
|
|
|
|
let value = json!({"id": "test_id", "type": "Note"});
|
|
|
|
assert_eq!(get_object_id(value).unwrap(), "test_id");
|
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|