3 use crate::settings::Settings;
4 use diesel::PgConnection;
6 #[derive(Serialize, Deserialize)]
7 pub struct CreateComment {
9 parent_id: Option<i32>,
10 edit_id: Option<i32>, // TODO this isn't used
15 #[derive(Serialize, Deserialize)]
16 pub struct EditComment {
18 parent_id: Option<i32>, // TODO why are the parent_id, creator_id, post_id, etc fields required? They aren't going to change
22 removed: Option<bool>,
23 deleted: Option<bool>,
24 reason: Option<String>,
29 #[derive(Serialize, Deserialize)]
30 pub struct SaveComment {
36 #[derive(Serialize, Deserialize, Clone)]
37 pub struct CommentResponse {
38 pub comment: CommentView,
39 pub recipient_ids: Vec<i32>,
42 #[derive(Serialize, Deserialize)]
43 pub struct CreateCommentLike {
50 impl Perform<CommentResponse> for Oper<CreateComment> {
51 fn perform(&self, conn: &PgConnection) -> Result<CommentResponse, Error> {
52 let data: &CreateComment = &self.data;
54 let claims = match Claims::decode(&data.auth) {
55 Ok(claims) => claims.claims,
56 Err(_e) => return Err(APIError::err("not_logged_in").into()),
59 let user_id = claims.id;
61 let hostname = &format!("https://{}", Settings::get().hostname);
63 // Check for a community ban
64 let post = Post::read(&conn, data.post_id)?;
65 if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
66 return Err(APIError::err("community_ban").into());
69 // Check for a site ban
70 if UserView::read(&conn, user_id)?.banned {
71 return Err(APIError::err("site_ban").into());
74 let content_slurs_removed = remove_slurs(&data.content.to_owned());
76 let comment_form = CommentForm {
77 content: content_slurs_removed,
78 parent_id: data.parent_id.to_owned(),
79 post_id: data.post_id,
87 let inserted_comment = match Comment::create(&conn, &comment_form) {
88 Ok(comment) => comment,
89 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
92 let mut recipient_ids = Vec::new();
94 // Scan the comment for user mentions, add those rows
95 let extracted_usernames = extract_usernames(&comment_form.content);
97 for username_mention in &extracted_usernames {
98 if let Ok(mention_user) = User_::read_from_name(&conn, (*username_mention).to_string()) {
99 // You can't mention yourself
100 // At some point, make it so you can't tag the parent creator either
101 // This can cause two notifications, one for reply and the other for mention
102 if mention_user.id != user_id {
103 recipient_ids.push(mention_user.id);
105 let user_mention_form = UserMentionForm {
106 recipient_id: mention_user.id,
107 comment_id: inserted_comment.id,
111 // Allow this to fail softly, since comment edits might re-update or replace it
112 // Let the uniqueness handle this fail
113 match UserMention::create(&conn, &user_mention_form) {
115 Err(_e) => eprintln!("{}", &_e),
118 // Send an email to those users that have notifications on
119 if mention_user.send_notifications_to_email {
120 if let Some(mention_email) = mention_user.email {
121 let subject = &format!(
122 "{} - Mentioned by {}",
123 Settings::get().hostname,
127 "<h1>User Mention</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
128 claims.username, comment_form.content, hostname
130 match send_email(subject, &mention_email, &mention_user.name, html) {
132 Err(e) => eprintln!("{}", e),
140 // Send notifs to the parent commenter / poster
141 match data.parent_id {
143 let parent_comment = Comment::read(&conn, parent_id)?;
144 if parent_comment.creator_id != user_id {
145 let parent_user = User_::read(&conn, parent_comment.creator_id)?;
146 recipient_ids.push(parent_user.id);
148 if parent_user.send_notifications_to_email {
149 if let Some(comment_reply_email) = parent_user.email {
150 let subject = &format!(
151 "{} - Reply from {}",
152 Settings::get().hostname,
156 "<h1>Comment Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
157 claims.username, comment_form.content, hostname
159 match send_email(subject, &comment_reply_email, &parent_user.name, html) {
161 Err(e) => eprintln!("{}", e),
169 if post.creator_id != user_id {
170 let parent_user = User_::read(&conn, post.creator_id)?;
171 recipient_ids.push(parent_user.id);
173 if parent_user.send_notifications_to_email {
174 if let Some(post_reply_email) = parent_user.email {
175 let subject = &format!(
176 "{} - Reply from {}",
177 Settings::get().hostname,
181 "<h1>Post Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
182 claims.username, comment_form.content, hostname
184 match send_email(subject, &post_reply_email, &parent_user.name, html) {
186 Err(e) => eprintln!("{}", e),
194 // You like your own comment by default
195 let like_form = CommentLikeForm {
196 comment_id: inserted_comment.id,
197 post_id: data.post_id,
202 let _inserted_like = match CommentLike::like(&conn, &like_form) {
204 Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
207 let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?;
210 comment: comment_view,
216 impl Perform<CommentResponse> for Oper<EditComment> {
217 fn perform(&self, conn: &PgConnection) -> Result<CommentResponse, Error> {
218 let data: &EditComment = &self.data;
220 let claims = match Claims::decode(&data.auth) {
221 Ok(claims) => claims.claims,
222 Err(_e) => return Err(APIError::err("not_logged_in").into()),
225 let user_id = claims.id;
227 let orig_comment = CommentView::read(&conn, data.edit_id, None)?;
229 // You are allowed to mark the comment as read even if you're banned.
230 if data.read.is_none() {
231 // Verify its the creator or a mod, or an admin
232 let mut editors: Vec<i32> = vec![data.creator_id];
234 &mut CommunityModeratorView::for_community(&conn, orig_comment.community_id)?
239 editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
241 if !editors.contains(&user_id) {
242 return Err(APIError::err("no_comment_edit_allowed").into());
245 // Check for a community ban
246 if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() {
247 return Err(APIError::err("community_ban").into());
250 // Check for a site ban
251 if UserView::read(&conn, user_id)?.banned {
252 return Err(APIError::err("site_ban").into());
256 let content_slurs_removed = remove_slurs(&data.content.to_owned());
258 let comment_form = CommentForm {
259 content: content_slurs_removed,
260 parent_id: data.parent_id,
261 post_id: data.post_id,
262 creator_id: data.creator_id,
263 removed: data.removed.to_owned(),
264 deleted: data.deleted.to_owned(),
265 read: data.read.to_owned(),
266 updated: if data.read.is_some() {
273 let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
274 Ok(comment) => comment,
275 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
278 let mut recipient_ids = Vec::new();
280 // Scan the comment for user mentions, add those rows
281 let extracted_usernames = extract_usernames(&comment_form.content);
283 for username_mention in &extracted_usernames {
284 let mention_user = User_::read_from_name(&conn, (*username_mention).to_string());
286 if mention_user.is_ok() {
287 let mention_user_id = mention_user?.id;
289 // You can't mention yourself
290 // At some point, make it so you can't tag the parent creator either
291 // This can cause two notifications, one for reply and the other for mention
292 if mention_user_id != user_id {
293 recipient_ids.push(mention_user_id);
295 let user_mention_form = UserMentionForm {
296 recipient_id: mention_user_id,
297 comment_id: data.edit_id,
301 // Allow this to fail softly, since comment edits might re-update or replace it
302 // Let the uniqueness handle this fail
303 match UserMention::create(&conn, &user_mention_form) {
305 Err(_e) => eprintln!("{}", &_e),
311 // Add to recipient ids
312 match data.parent_id {
314 let parent_comment = Comment::read(&conn, parent_id)?;
315 if parent_comment.creator_id != user_id {
316 let parent_user = User_::read(&conn, parent_comment.creator_id)?;
317 recipient_ids.push(parent_user.id);
321 let post = Post::read(&conn, data.post_id)?;
322 recipient_ids.push(post.creator_id);
327 if let Some(removed) = data.removed.to_owned() {
328 let form = ModRemoveCommentForm {
329 mod_user_id: user_id,
330 comment_id: data.edit_id,
331 removed: Some(removed),
332 reason: data.reason.to_owned(),
334 ModRemoveComment::create(&conn, &form)?;
337 let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?;
340 comment: comment_view,
346 impl Perform<CommentResponse> for Oper<SaveComment> {
347 fn perform(&self, conn: &PgConnection) -> Result<CommentResponse, Error> {
348 let data: &SaveComment = &self.data;
350 let claims = match Claims::decode(&data.auth) {
351 Ok(claims) => claims.claims,
352 Err(_e) => return Err(APIError::err("not_logged_in").into()),
355 let user_id = claims.id;
357 let comment_saved_form = CommentSavedForm {
358 comment_id: data.comment_id,
363 match CommentSaved::save(&conn, &comment_saved_form) {
364 Ok(comment) => comment,
365 Err(_e) => return Err(APIError::err("couldnt_save_comment").into()),
368 match CommentSaved::unsave(&conn, &comment_saved_form) {
369 Ok(comment) => comment,
370 Err(_e) => return Err(APIError::err("couldnt_save_comment").into()),
374 let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?;
377 comment: comment_view,
378 recipient_ids: Vec::new(),
383 impl Perform<CommentResponse> for Oper<CreateCommentLike> {
384 fn perform(&self, conn: &PgConnection) -> Result<CommentResponse, Error> {
385 let data: &CreateCommentLike = &self.data;
387 let claims = match Claims::decode(&data.auth) {
388 Ok(claims) => claims.claims,
389 Err(_e) => return Err(APIError::err("not_logged_in").into()),
392 let user_id = claims.id;
394 let mut recipient_ids = Vec::new();
396 // Don't do a downvote if site has downvotes disabled
397 if data.score == -1 {
398 let site = SiteView::read(&conn)?;
399 if !site.enable_downvotes {
400 return Err(APIError::err("downvotes_disabled").into());
404 // Check for a community ban
405 let post = Post::read(&conn, data.post_id)?;
406 if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
407 return Err(APIError::err("community_ban").into());
410 // Check for a site ban
411 if UserView::read(&conn, user_id)?.banned {
412 return Err(APIError::err("site_ban").into());
415 let comment = Comment::read(&conn, data.comment_id)?;
417 // Add to recipient ids
418 match comment.parent_id {
420 let parent_comment = Comment::read(&conn, parent_id)?;
421 if parent_comment.creator_id != user_id {
422 let parent_user = User_::read(&conn, parent_comment.creator_id)?;
423 recipient_ids.push(parent_user.id);
427 recipient_ids.push(post.creator_id);
431 let like_form = CommentLikeForm {
432 comment_id: data.comment_id,
433 post_id: data.post_id,
438 // Remove any likes first
439 CommentLike::remove(&conn, &like_form)?;
441 // Only add the like if the score isnt 0
442 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
444 let _inserted_like = match CommentLike::like(&conn, &like_form) {
446 Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
450 // Have to refetch the comment to get the current state
451 let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?;
454 comment: liked_comment,