]> Untitled Git - lemmy.git/blob - crates/api_crud/src/comment/create.rs
Merge crates db_schema and db_queries
[lemmy.git] / crates / api_crud / src / comment / create.rs
1 use crate::PerformCrud;
2 use actix_web::web::Data;
3 use lemmy_api_common::{
4   blocking,
5   check_community_ban,
6   check_community_deleted_or_removed,
7   check_person_block,
8   check_post_deleted_or_removed,
9   comment::*,
10   get_local_user_view_from_jwt,
11   get_post,
12   send_local_notifs,
13 };
14 use lemmy_apub::{
15   activities::{
16     comment::create_or_update::CreateOrUpdateComment,
17     voting::vote::{Vote, VoteType},
18     CreateOrUpdateType,
19   },
20   fetcher::post_or_comment::PostOrComment,
21   generate_apub_endpoint,
22   EndpointType,
23 };
24 use lemmy_db_schema::{
25   source::{
26     comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
27     person_mention::PersonMention,
28   },
29   traits::{Crud, Likeable},
30 };
31 use lemmy_db_views::comment_view::CommentView;
32 use lemmy_utils::{
33   utils::{remove_slurs, scrape_text_for_mentions},
34   ApiError,
35   ConnectionId,
36   LemmyError,
37 };
38 use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
39
40 #[async_trait::async_trait(?Send)]
41 impl PerformCrud for CreateComment {
42   type Response = CommentResponse;
43
44   async fn perform(
45     &self,
46     context: &Data<LemmyContext>,
47     websocket_id: Option<ConnectionId>,
48   ) -> Result<CommentResponse, LemmyError> {
49     let data: &CreateComment = self;
50     let local_user_view =
51       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
52
53     let content_slurs_removed =
54       remove_slurs(&data.content.to_owned(), &context.settings().slur_regex());
55
56     // Check for a community ban
57     let post_id = data.post_id;
58     let post = get_post(post_id, context.pool()).await?;
59     let community_id = post.community_id;
60
61     check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
62     check_community_deleted_or_removed(community_id, context.pool()).await?;
63     check_post_deleted_or_removed(&post)?;
64
65     check_person_block(local_user_view.person.id, post.creator_id, context.pool()).await?;
66
67     // Check if post is locked, no new comments
68     if post.locked {
69       return Err(ApiError::err_plain("locked").into());
70     }
71
72     // If there's a parent_id, check to make sure that comment is in that post
73     if let Some(parent_id) = data.parent_id {
74       // Make sure the parent comment exists
75       let parent = blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
76         .await?
77         .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
78
79       check_person_block(local_user_view.person.id, parent.creator_id, context.pool()).await?;
80
81       // Strange issue where sometimes the post ID is incorrect
82       if parent.post_id != post_id {
83         return Err(ApiError::err_plain("couldnt_create_comment").into());
84       }
85     }
86
87     let comment_form = CommentForm {
88       content: content_slurs_removed,
89       parent_id: data.parent_id.to_owned(),
90       post_id: data.post_id,
91       creator_id: local_user_view.person.id,
92       ..CommentForm::default()
93     };
94
95     // Create the comment
96     let comment_form2 = comment_form.clone();
97     let inserted_comment = blocking(context.pool(), move |conn| {
98       Comment::create(conn, &comment_form2)
99     })
100     .await?
101     .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
102
103     // Necessary to update the ap_id
104     let inserted_comment_id = inserted_comment.id;
105     let protocol_and_hostname = context.settings().get_protocol_and_hostname();
106
107     let updated_comment: Comment =
108       blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
109         let apub_id = generate_apub_endpoint(
110           EndpointType::Comment,
111           &inserted_comment_id.to_string(),
112           &protocol_and_hostname,
113         )?;
114         Ok(Comment::update_ap_id(conn, inserted_comment_id, apub_id)?)
115       })
116       .await?
117       .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
118
119     CreateOrUpdateComment::send(
120       &updated_comment,
121       &local_user_view.person,
122       CreateOrUpdateType::Create,
123       context,
124     )
125     .await?;
126
127     // Scan the comment for user mentions, add those rows
128     let post_id = post.id;
129     let mentions = scrape_text_for_mentions(&comment_form.content);
130     let recipient_ids = send_local_notifs(
131       mentions,
132       updated_comment.clone(),
133       local_user_view.person.clone(),
134       post,
135       context.pool(),
136       true,
137       &context.settings(),
138     )
139     .await?;
140
141     // You like your own comment by default
142     let like_form = CommentLikeForm {
143       comment_id: inserted_comment.id,
144       post_id,
145       person_id: local_user_view.person.id,
146       score: 1,
147     };
148
149     let like = move |conn: &'_ _| CommentLike::like(conn, &like_form);
150     blocking(context.pool(), like)
151       .await?
152       .map_err(|e| ApiError::err("couldnt_like_comment", e))?;
153
154     let object = PostOrComment::Comment(updated_comment);
155     Vote::send(
156       &object,
157       &local_user_view.person,
158       community_id,
159       VoteType::Like,
160       context,
161     )
162     .await?;
163
164     let person_id = local_user_view.person.id;
165     let comment_id = inserted_comment.id;
166     let comment_view = blocking(context.pool(), move |conn| {
167       CommentView::read(conn, comment_id, Some(person_id))
168     })
169     .await??;
170
171     // If its a comment to yourself, mark it as read
172     if local_user_view.person.id == comment_view.get_recipient_id() {
173       let comment_id = inserted_comment.id;
174       blocking(context.pool(), move |conn| {
175         Comment::update_read(conn, comment_id, true)
176       })
177       .await?
178       .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
179     }
180     // If its a reply, mark the parent as read
181     if let Some(parent_id) = data.parent_id {
182       let parent_comment = blocking(context.pool(), move |conn| {
183         CommentView::read(conn, parent_id, Some(person_id))
184       })
185       .await??;
186       if local_user_view.person.id == parent_comment.get_recipient_id() {
187         blocking(context.pool(), move |conn| {
188           Comment::update_read(conn, parent_id, true)
189         })
190         .await?
191         .map_err(|e| ApiError::err("couldnt_update_parent_comment", e))?;
192       }
193       // If the parent has PersonMentions mark them as read too
194       let person_id = local_user_view.person.id;
195       let person_mention = blocking(context.pool(), move |conn| {
196         PersonMention::read_by_comment_and_person(conn, parent_id, person_id)
197       })
198       .await?;
199       if let Ok(mention) = person_mention {
200         blocking(context.pool(), move |conn| {
201           PersonMention::update_read(conn, mention.id, true)
202         })
203         .await?
204         .map_err(|e| ApiError::err("couldnt_update_person_mentions", e))?;
205       }
206     }
207
208     send_comment_ws_message(
209       inserted_comment.id,
210       UserOperationCrud::CreateComment,
211       websocket_id,
212       data.form_id.to_owned(),
213       Some(local_user_view.person.id),
214       recipient_ids,
215       context,
216     )
217     .await
218   }
219 }