]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/update.rs
When receiving activity, dont read community from cc (for pleroma compat and better...
[lemmy.git] / crates / apub / src / activities / community / update.rs
1 use crate::{
2   activities::{
3     community::{
4       announce::{AnnouncableActivities, GetCommunity},
5       send_to_community,
6     },
7     generate_activity_id,
8     verify_activity,
9     verify_is_public,
10     verify_mod_action,
11     verify_person_in_community,
12   },
13   context::lemmy_context,
14   fetcher::object_id::ObjectId,
15   objects::{
16     community::{ApubCommunity, Group},
17     person::ApubPerson,
18   },
19 };
20 use activitystreams::{
21   activity::kind::UpdateType,
22   base::AnyBase,
23   primitives::OneOrMany,
24   public,
25   unparsed::Unparsed,
26 };
27 use lemmy_api_common::blocking;
28 use lemmy_apub_lib::{
29   data::Data,
30   traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
31 };
32 use lemmy_db_schema::{
33   source::community::{Community, CommunityForm},
34   traits::Crud,
35 };
36 use lemmy_utils::LemmyError;
37 use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
38 use serde::{Deserialize, Serialize};
39 use url::Url;
40
41 /// This activity is received from a remote community mod, and updates the description or other
42 /// fields of a local community.
43 #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
44 #[serde(rename_all = "camelCase")]
45 pub struct UpdateCommunity {
46   actor: ObjectId<ApubPerson>,
47   to: Vec<Url>,
48   // TODO: would be nice to use a separate struct here, which only contains the fields updated here
49   object: Group,
50   cc: [ObjectId<ApubCommunity>; 1],
51   #[serde(rename = "type")]
52   kind: UpdateType,
53   id: Url,
54   #[serde(rename = "@context")]
55   context: OneOrMany<AnyBase>,
56   #[serde(flatten)]
57   unparsed: Unparsed,
58 }
59
60 impl UpdateCommunity {
61   pub async fn send(
62     community: &ApubCommunity,
63     actor: &ApubPerson,
64     context: &LemmyContext,
65   ) -> Result<(), LemmyError> {
66     let id = generate_activity_id(
67       UpdateType::Update,
68       &context.settings().get_protocol_and_hostname(),
69     )?;
70     let update = UpdateCommunity {
71       actor: ObjectId::new(actor.actor_id()),
72       to: vec![public()],
73       object: community.to_apub(context).await?,
74       cc: [ObjectId::new(community.actor_id())],
75       kind: UpdateType::Update,
76       id: id.clone(),
77       context: lemmy_context(),
78       unparsed: Default::default(),
79     };
80
81     let activity = AnnouncableActivities::UpdateCommunity(Box::new(update));
82     send_to_community(activity, &id, actor, community, vec![], context).await
83   }
84 }
85
86 #[async_trait::async_trait(?Send)]
87 impl ActivityHandler for UpdateCommunity {
88   type DataType = LemmyContext;
89   async fn verify(
90     &self,
91     context: &Data<LemmyContext>,
92     request_counter: &mut i32,
93   ) -> Result<(), LemmyError> {
94     verify_is_public(&self.to)?;
95     verify_activity(self, &context.settings())?;
96     let community = self.get_community(context, request_counter).await?;
97     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
98     verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
99     Ok(())
100   }
101
102   async fn receive(
103     self,
104     context: &Data<LemmyContext>,
105     request_counter: &mut i32,
106   ) -> Result<(), LemmyError> {
107     let community = self.get_community(context, request_counter).await?;
108
109     let updated_community = Group::from_apub_to_form(
110       &self.object,
111       &community.actor_id.clone().into(),
112       &context.settings(),
113     )
114     .await?;
115     let cf = CommunityForm {
116       name: updated_community.name,
117       title: updated_community.title,
118       description: updated_community.description,
119       nsfw: updated_community.nsfw,
120       // TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours
121       icon: updated_community.icon,
122       banner: updated_community.banner,
123       ..CommunityForm::default()
124     };
125     let updated_community = blocking(context.pool(), move |conn| {
126       Community::update(conn, community.id, &cf)
127     })
128     .await??;
129
130     send_community_ws_message(
131       updated_community.id,
132       UserOperationCrud::EditCommunity,
133       None,
134       None,
135       context,
136     )
137     .await?;
138     Ok(())
139   }
140 }
141
142 #[async_trait::async_trait(?Send)]
143 impl GetCommunity for UpdateCommunity {
144   async fn get_community(
145     &self,
146     context: &LemmyContext,
147     request_counter: &mut i32,
148   ) -> Result<ApubCommunity, LemmyError> {
149     let cid = ObjectId::new(self.object.id.clone());
150     cid.dereference(context, request_counter).await
151   }
152 }