Refactor create_view()

This commit is contained in:
silverpill 2022-12-19 23:30:54 +00:00
parent 20e965a655
commit 748521b5ce
2 changed files with 72 additions and 102 deletions

View file

@ -2,12 +2,10 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::errors::ValidationError;
use crate::mastodon_api::accounts::types::Account; use crate::mastodon_api::accounts::types::Account;
use crate::mastodon_api::media::types::Attachment; use crate::mastodon_api::media::types::Attachment;
use crate::models::posts::types::{Post, PostCreateData, Visibility}; use crate::models::posts::types::{Post, Visibility};
use crate::models::profiles::types::DbActorProfile; use crate::models::profiles::types::DbActorProfile;
use crate::utils::markdown::markdown_lite_to_html;
/// https://docs.joinmastodon.org/entities/mention/ /// https://docs.joinmastodon.org/entities/mention/
#[derive(Serialize)] #[derive(Serialize)]
@ -151,68 +149,7 @@ pub struct StatusData {
pub content_type: String, pub content_type: String,
} }
impl TryFrom<StatusData> for PostCreateData {
type Error = ValidationError;
fn try_from(status_data: StatusData) -> Result<Self, Self::Error> {
let visibility = match status_data.visibility.as_deref() {
Some("public") => Visibility::Public,
Some("direct") => Visibility::Direct,
Some("private") => Visibility::Followers,
Some("subscribers") => Visibility::Subscribers,
Some(_) => return Err(ValidationError("invalid visibility parameter")),
None => Visibility::Public,
};
let content = match status_data.content_type.as_str() {
"text/html" => status_data.status,
"text/markdown" => {
markdown_lite_to_html(&status_data.status)
.map_err(|_| ValidationError("invalid markdown"))?
},
_ => return Err(ValidationError("unsupported content type")),
};
let post_data = Self {
content: content,
in_reply_to_id: status_data.in_reply_to_id,
repost_of_id: None,
visibility: visibility,
attachments: status_data.media_ids.unwrap_or(vec![]),
mentions: status_data.mentions.unwrap_or(vec![]),
tags: vec![],
links: vec![],
object_id: None,
created_at: Utc::now(),
};
Ok(post_data)
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct TransactionData { pub struct TransactionData {
pub transaction_id: String, pub transaction_id: String,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_data_into_post_data() {
let status_content = "<p>test</p>";
let status_data = StatusData {
status: status_content.to_string(),
media_ids: None,
in_reply_to_id: None,
visibility: Some("public".to_string()),
mentions: None,
content_type: "text/html".to_string(),
};
let post_data = PostCreateData::try_from(status_data).unwrap();
assert_eq!(post_data.content, status_content);
assert_eq!(post_data.visibility, Visibility::Public);
assert_eq!(post_data.attachments, vec![]);
assert_eq!(post_data.mentions, vec![]);
assert_eq!(post_data.links, vec![]);
}
}

View file

@ -1,6 +1,7 @@
/// https://docs.joinmastodon.org/methods/statuses/ /// https://docs.joinmastodon.org/methods/statuses/
use actix_web::{delete, get, post, web, HttpResponse, Scope}; use actix_web::{delete, get, post, web, HttpResponse, Scope};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
use crate::activitypub::builders::{ use crate::activitypub::builders::{
@ -39,7 +40,10 @@ use crate::models::reactions::queries::{
delete_reaction, delete_reaction,
}; };
use crate::models::relationships::queries::get_subscribers; use crate::models::relationships::queries::get_subscribers;
use crate::utils::currencies::Currency; use crate::utils::{
currencies::Currency,
markdown::markdown_lite_to_html,
};
use super::helpers::{ use super::helpers::{
build_status, build_status,
build_status_list, build_status_list,
@ -56,58 +60,78 @@ async fn create_status(
let db_client = &mut **get_database_client(&db_pool).await?; let db_client = &mut **get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?; let current_user = get_current_user(db_client, auth.token()).await?;
let instance = config.instance(); let instance = config.instance();
let mut post_data = PostCreateData::try_from(status_data.into_inner())?; let status_data = status_data.into_inner();
let visibility = match status_data.visibility.as_deref() {
Some("public") => Visibility::Public,
Some("direct") => Visibility::Direct,
Some("private") => Visibility::Followers,
Some("subscribers") => Visibility::Subscribers,
Some(_) => return Err(ValidationError("invalid visibility parameter").into()),
None => Visibility::Public,
};
let mut content = match status_data.content_type.as_str() {
"text/html" => status_data.status,
"text/markdown" => {
markdown_lite_to_html(&status_data.status)
.map_err(|_| ValidationError("invalid markdown"))?
},
_ => return Err(ValidationError("unsupported content type").into()),
};
let mut mentions = status_data.mentions.unwrap_or(vec![]);
// Mentions // Mentions
let mention_map = find_mentioned_profiles( let mention_map = find_mentioned_profiles(
db_client, db_client,
&instance.hostname(), &instance.hostname(),
&post_data.content, &content,
).await?; ).await?;
post_data.content = replace_mentions( content = replace_mentions(
&mention_map, &mention_map,
&instance.hostname(), &instance.hostname(),
&instance.url(), &instance.url(),
&post_data.content, &content,
); );
post_data.mentions.extend(mention_map.values() mentions.extend(mention_map.values().map(|profile| profile.id));
.map(|profile| profile.id)); // Hashtags
if post_data.visibility == Visibility::Subscribers { let tags = find_hashtags(&content);
content = replace_hashtags(
&instance.url(),
&content,
&tags,
);
// Links
let link_map = find_linked_posts(
db_client,
&instance.url(),
&content,
).await?;
content = replace_object_links(
&link_map,
&content,
);
let links: Vec<_> = link_map.values().map(|post| post.id).collect();
let linked = link_map.into_values().collect();
// Clean content
content = clean_content(&content)?;
if visibility == Visibility::Subscribers {
// Mention all subscribers. // Mention all subscribers.
// This makes post accessible only to active subscribers // This makes post accessible only to active subscribers
// and is required for sending activities to subscribers // and is required for sending activities to subscribers
// on other instances. // on other instances.
let subscribers = get_subscribers(db_client, &current_user.id).await? let subscribers = get_subscribers(db_client, &current_user.id).await?
.into_iter().map(|profile| profile.id); .into_iter().map(|profile| profile.id);
post_data.mentions.extend(subscribers); mentions.extend(subscribers);
}; };
// Hashtags // Remove duplicate mentions
post_data.tags = find_hashtags(&post_data.content); mentions.sort();
post_data.content = replace_hashtags( mentions.dedup();
&instance.url(),
&post_data.content,
&post_data.tags,
);
// Links
let link_map = find_linked_posts(
db_client,
&instance.url(),
&post_data.content,
).await?;
post_data.content = replace_object_links(
&link_map,
&post_data.content,
);
post_data.links.extend(link_map.values().map(|post| post.id));
let linked = link_map.into_values().collect();
// Clean content
post_data.content = clean_content(&post_data.content)?;
// Links validation // Links validation
if post_data.links.len() > 0 && post_data.visibility != Visibility::Public { if links.len() > 0 && visibility != Visibility::Public {
return Err(ValidationError("can't add links to non-public posts").into()); return Err(ValidationError("can't add links to non-public posts").into());
}; };
// Reply validation // Reply validation
let maybe_in_reply_to = if let Some(in_reply_to_id) = post_data.in_reply_to_id.as_ref() { let maybe_in_reply_to = if let Some(in_reply_to_id) = status_data.in_reply_to_id.as_ref() {
let in_reply_to = match get_post_by_id(db_client, in_reply_to_id).await { let in_reply_to = match get_post_by_id(db_client, in_reply_to_id).await {
Ok(post) => post, Ok(post) => post,
Err(DatabaseError::NotFound(_)) => { Err(DatabaseError::NotFound(_)) => {
@ -119,14 +143,14 @@ async fn create_status(
return Err(ValidationError("can't reply to repost").into()); return Err(ValidationError("can't reply to repost").into());
}; };
if in_reply_to.visibility != Visibility::Public && if in_reply_to.visibility != Visibility::Public &&
post_data.visibility != Visibility::Direct { visibility != Visibility::Direct {
return Err(ValidationError("reply must have direct visibility").into()); return Err(ValidationError("reply must have direct visibility").into());
}; };
if post_data.visibility != Visibility::Public { if visibility != Visibility::Public {
let mut in_reply_to_audience: Vec<_> = in_reply_to.mentions.iter() let mut in_reply_to_audience: Vec<_> = in_reply_to.mentions.iter()
.map(|profile| profile.id).collect(); .map(|profile| profile.id).collect();
in_reply_to_audience.push(in_reply_to.author.id); in_reply_to_audience.push(in_reply_to.author.id);
if !post_data.mentions.iter().all(|id| in_reply_to_audience.contains(id)) { if !mentions.iter().all(|id| in_reply_to_audience.contains(id)) {
return Err(ValidationError("audience can't be expanded").into()); return Err(ValidationError("audience can't be expanded").into());
}; };
}; };
@ -134,11 +158,20 @@ async fn create_status(
} else { } else {
None None
}; };
// Remove duplicate mentions
post_data.mentions.sort();
post_data.mentions.dedup();
// Create post // Create post
let post_data = PostCreateData {
content: content,
in_reply_to_id: status_data.in_reply_to_id,
repost_of_id: None,
visibility: visibility,
attachments: status_data.media_ids.unwrap_or(vec![]),
mentions: mentions,
tags: tags,
links: links,
object_id: None,
created_at: Utc::now(),
};
let mut post = create_post(db_client, &current_user.id, post_data).await?; let mut post = create_post(db_client, &current_user.id, post_data).await?;
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| { post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
in_reply_to.reply_count += 1; in_reply_to.reply_count += 1;