]> Untitled Git - lemmy.git/blob - crates/api_crud/src/post/create.rs
Remove SendActivity and Perform traits, rely on channel (#3596)
[lemmy.git] / crates / api_crud / src / post / create.rs
1 use activitypub_federation::config::Data;
2 use actix_web::web::Json;
3 use lemmy_api_common::{
4   build_response::build_post_response,
5   context::LemmyContext,
6   post::{CreatePost, PostResponse},
7   request::fetch_site_data,
8   send_activity::{ActivityChannel, SendActivityData},
9   utils::{
10     check_community_ban,
11     check_community_deleted_or_removed,
12     generate_local_apub_endpoint,
13     honeypot_check,
14     local_site_to_slur_regex,
15     local_user_view_from_jwt,
16     mark_post_as_read,
17     EndpointType,
18   },
19 };
20 use lemmy_db_schema::{
21   impls::actor_language::default_post_language,
22   source::{
23     actor_language::CommunityLanguage,
24     community::Community,
25     local_site::LocalSite,
26     post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
27   },
28   traits::{Crud, Likeable},
29 };
30 use lemmy_db_views_actor::structs::CommunityView;
31 use lemmy_utils::{
32   error::{LemmyError, LemmyErrorExt, LemmyErrorType},
33   spawn_try_task,
34   utils::{
35     slurs::{check_slurs, check_slurs_opt},
36     validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title},
37   },
38   SYNCHRONOUS_FEDERATION,
39 };
40 use tracing::Instrument;
41 use url::Url;
42 use webmention::{Webmention, WebmentionError};
43
44 #[tracing::instrument(skip(context))]
45 pub async fn create_post(
46   data: Json<CreatePost>,
47   context: Data<LemmyContext>,
48 ) -> Result<Json<PostResponse>, LemmyError> {
49   let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
50   let local_site = LocalSite::read(&mut context.pool()).await?;
51
52   let slur_regex = local_site_to_slur_regex(&local_site);
53   check_slurs(&data.name, &slur_regex)?;
54   check_slurs_opt(&data.body, &slur_regex)?;
55   honeypot_check(&data.honeypot)?;
56
57   let data_url = data.url.as_ref();
58   let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
59
60   is_valid_post_title(&data.name)?;
61   is_valid_body_field(&data.body, true)?;
62   check_url_scheme(&data.url)?;
63
64   check_community_ban(
65     local_user_view.person.id,
66     data.community_id,
67     &mut context.pool(),
68   )
69   .await?;
70   check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
71
72   let community_id = data.community_id;
73   let community = Community::read(&mut context.pool(), community_id).await?;
74   if community.posting_restricted_to_mods {
75     let community_id = data.community_id;
76     let is_mod = CommunityView::is_mod_or_admin(
77       &mut context.pool(),
78       local_user_view.local_user.person_id,
79       community_id,
80     )
81     .await?;
82     if !is_mod {
83       return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
84     }
85   }
86
87   // Fetch post links and pictrs cached image
88   let (metadata_res, thumbnail_url) =
89     fetch_site_data(context.client(), context.settings(), data_url, true).await;
90   let (embed_title, embed_description, embed_video_url) = metadata_res
91     .map(|u| (u.title, u.description, u.embed_video_url))
92     .unwrap_or_default();
93
94   let language_id = match data.language_id {
95     Some(lid) => Some(lid),
96     None => {
97       default_post_language(
98         &mut context.pool(),
99         community_id,
100         local_user_view.local_user.id,
101       )
102       .await?
103     }
104   };
105   CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id)
106     .await?;
107
108   let post_form = PostInsertForm::builder()
109     .name(data.name.trim().to_owned())
110     .url(url)
111     .body(data.body.clone())
112     .community_id(data.community_id)
113     .creator_id(local_user_view.person.id)
114     .nsfw(data.nsfw)
115     .embed_title(embed_title)
116     .embed_description(embed_description)
117     .embed_video_url(embed_video_url)
118     .language_id(language_id)
119     .thumbnail_url(thumbnail_url)
120     .build();
121
122   let inserted_post = Post::create(&mut context.pool(), &post_form)
123     .await
124     .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
125
126   let inserted_post_id = inserted_post.id;
127   let protocol_and_hostname = context.settings().get_protocol_and_hostname();
128   let apub_id = generate_local_apub_endpoint(
129     EndpointType::Post,
130     &inserted_post_id.to_string(),
131     &protocol_and_hostname,
132   )?;
133   let updated_post = Post::update(
134     &mut context.pool(),
135     inserted_post_id,
136     &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
137   )
138   .await
139   .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
140
141   // They like their own post by default
142   let person_id = local_user_view.person.id;
143   let post_id = inserted_post.id;
144   let like_form = PostLikeForm {
145     post_id,
146     person_id,
147     score: 1,
148   };
149
150   PostLike::like(&mut context.pool(), &like_form)
151     .await
152     .with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
153
154   ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
155     .await?;
156
157   // Mark the post as read
158   mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
159
160   if let Some(url) = updated_post.url.clone() {
161     let task = async move {
162       let mut webmention =
163         Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
164       webmention.set_checked(true);
165       match webmention
166         .send()
167         .instrument(tracing::info_span!("Sending webmention"))
168         .await
169       {
170         Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
171         Ok(_) => Ok(()),
172         Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
173       }
174     };
175     if *SYNCHRONOUS_FEDERATION {
176       task.await?;
177     } else {
178       spawn_try_task(task);
179     }
180   };
181
182   Ok(Json(
183     build_post_response(&context, community_id, person_id, post_id).await?,
184   ))
185 }