]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/delete.rs
Automatically resolve report when post/comment is removed (#3850)
[lemmy.git] / crates / apub / src / activities / deletion / delete.rs
1 use crate::{
2   activities::{
3     deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
4     generate_activity_id,
5   },
6   insert_received_activity,
7   objects::person::ApubPerson,
8   protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
9 };
10 use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
11 use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_opt};
12 use lemmy_db_schema::{
13   source::{
14     comment::{Comment, CommentUpdateForm},
15     comment_report::CommentReport,
16     community::{Community, CommunityUpdateForm},
17     moderator::{
18       ModRemoveComment,
19       ModRemoveCommentForm,
20       ModRemoveCommunity,
21       ModRemoveCommunityForm,
22       ModRemovePost,
23       ModRemovePostForm,
24     },
25     post::{Post, PostUpdateForm},
26     post_report::PostReport,
27   },
28   traits::{Crud, Reportable},
29 };
30 use lemmy_utils::error::{LemmyError, LemmyErrorType};
31 use url::Url;
32
33 #[async_trait::async_trait]
34 impl ActivityHandler for Delete {
35   type DataType = LemmyContext;
36   type Error = LemmyError;
37
38   fn id(&self) -> &Url {
39     &self.id
40   }
41
42   fn actor(&self) -> &Url {
43     self.actor.inner()
44   }
45
46   #[tracing::instrument(skip_all)]
47   async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
48     insert_received_activity(&self.id, context).await?;
49     verify_delete_activity(self, self.summary.is_some(), context).await?;
50     Ok(())
51   }
52
53   #[tracing::instrument(skip_all)]
54   async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
55     if let Some(reason) = self.summary {
56       // We set reason to empty string if it doesn't exist, to distinguish between delete and
57       // remove. Here we change it back to option, so we don't write it to db.
58       let reason = if reason.is_empty() {
59         None
60       } else {
61         Some(reason)
62       };
63       receive_remove_action(
64         &self.actor.dereference(context).await?,
65         self.object.id(),
66         reason,
67         context,
68       )
69       .await
70     } else {
71       receive_delete_action(self.object.id(), &self.actor, true, context).await
72     }
73   }
74 }
75
76 impl Delete {
77   pub(in crate::activities::deletion) fn new(
78     actor: &ApubPerson,
79     object: DeletableObjects,
80     to: Url,
81     community: Option<&Community>,
82     summary: Option<String>,
83     context: &Data<LemmyContext>,
84   ) -> Result<Delete, LemmyError> {
85     let id = generate_activity_id(
86       DeleteType::Delete,
87       &context.settings().get_protocol_and_hostname(),
88     )?;
89     let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
90     Ok(Delete {
91       actor: actor.actor_id.clone().into(),
92       to: vec![to],
93       object: IdOrNestedObject::Id(object.id()),
94       cc: cc.into_iter().collect(),
95       kind: DeleteType::Delete,
96       summary,
97       id,
98       audience: community.map(|c| c.actor_id.clone().into()),
99     })
100   }
101 }
102
103 #[tracing::instrument(skip_all)]
104 pub(in crate::activities) async fn receive_remove_action(
105   actor: &ApubPerson,
106   object: &Url,
107   reason: Option<String>,
108   context: &Data<LemmyContext>,
109 ) -> Result<(), LemmyError> {
110   let reason = sanitize_html_opt(&reason);
111
112   match DeletableObjects::read_from_db(object, context).await? {
113     DeletableObjects::Community(community) => {
114       if community.local {
115         return Err(LemmyErrorType::OnlyLocalAdminCanRemoveCommunity)?;
116       }
117       let form = ModRemoveCommunityForm {
118         mod_person_id: actor.id,
119         community_id: community.id,
120         removed: Some(true),
121         reason,
122         expires: None,
123       };
124       ModRemoveCommunity::create(&mut context.pool(), &form).await?;
125       Community::update(
126         &mut context.pool(),
127         community.id,
128         &CommunityUpdateForm {
129           removed: Some(true),
130           ..Default::default()
131         },
132       )
133       .await?;
134     }
135     DeletableObjects::Post(post) => {
136       PostReport::resolve_all_for_object(&mut context.pool(), post.id, actor.id).await?;
137       let form = ModRemovePostForm {
138         mod_person_id: actor.id,
139         post_id: post.id,
140         removed: Some(true),
141         reason,
142       };
143       ModRemovePost::create(&mut context.pool(), &form).await?;
144       Post::update(
145         &mut context.pool(),
146         post.id,
147         &PostUpdateForm {
148           removed: Some(true),
149           ..Default::default()
150         },
151       )
152       .await?;
153     }
154     DeletableObjects::Comment(comment) => {
155       CommentReport::resolve_all_for_object(&mut context.pool(), comment.id, actor.id).await?;
156       let form = ModRemoveCommentForm {
157         mod_person_id: actor.id,
158         comment_id: comment.id,
159         removed: Some(true),
160         reason,
161       };
162       ModRemoveComment::create(&mut context.pool(), &form).await?;
163       Comment::update(
164         &mut context.pool(),
165         comment.id,
166         &CommentUpdateForm {
167           removed: Some(true),
168           ..Default::default()
169         },
170       )
171       .await?;
172     }
173     DeletableObjects::PrivateMessage(_) => unimplemented!(),
174   }
175   Ok(())
176 }