2 extensions::{context::lemmy_context, group_extensions::GroupExtension},
3 fetcher::{community::fetch_community_mods, user::get_or_fetch_and_upsert_user},
4 generate_moderators_url,
9 get_source_markdown_value,
10 set_content_and_source,
18 use activitystreams::{
19 actor::{kind::GroupType, ApActor, Endpoints, Group},
21 object::{ApObject, Image, Tombstone},
24 use activitystreams_ext::Ext2;
26 use lemmy_api_structs::blocking;
27 use lemmy_db_queries::{DbPool, Joinable};
28 use lemmy_db_schema::{
30 source::community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
33 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
36 utils::{check_slurs, check_slurs_opt, convert_datetime},
39 use lemmy_websocket::LemmyContext;
42 #[async_trait::async_trait(?Send)]
43 impl ToApub for Community {
44 type ApubType = GroupExt;
46 async fn to_apub(&self, _pool: &DbPool) -> Result<GroupExt, LemmyError> {
47 let mut group = ApObject::new(Group::new());
49 .set_many_contexts(lemmy_context()?)
50 .set_id(self.actor_id.to_owned().into())
51 .set_name(self.title.to_owned())
52 .set_published(convert_datetime(self.published));
54 if let Some(u) = self.updated.to_owned() {
55 group.set_updated(convert_datetime(u));
57 if let Some(d) = self.description.to_owned() {
58 set_content_and_source(&mut group, &d)?;
61 if let Some(icon_url) = &self.icon {
62 let mut image = Image::new();
63 image.set_url::<Url>(icon_url.to_owned().into());
64 group.set_icon(image.into_any_base()?);
67 if let Some(banner_url) = &self.banner {
68 let mut image = Image::new();
69 image.set_url::<Url>(banner_url.to_owned().into());
70 group.set_image(image.into_any_base()?);
73 let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group);
75 .set_preferred_username(self.name.to_owned())
76 .set_outbox(self.get_outbox_url()?)
77 .set_followers(self.followers_url.clone().into())
78 .set_endpoints(Endpoints {
79 shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
85 GroupExtension::new(self.nsfw, generate_moderators_url(&self.actor_id)?.into())?,
86 self.get_public_key_ext()?,
90 fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
93 self.actor_id.to_owned().into(),
100 #[async_trait::async_trait(?Send)]
101 impl FromApub for Community {
102 type ApubType = GroupExt;
104 /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
107 context: &LemmyContext,
108 expected_domain: Option<Url>,
109 request_counter: &mut i32,
110 ) -> Result<Community, LemmyError> {
111 let community: Community =
112 get_object_from_apub(group, context, expected_domain, request_counter).await?;
114 let new_moderators = fetch_community_mods(context, group, request_counter).await?;
115 let community_id = community.id;
116 let current_moderators = blocking(context.pool(), move |conn| {
117 CommunityModeratorView::for_community(&conn, community_id)
120 // Remove old mods from database which arent in the moderators collection anymore
121 for mod_user in ¤t_moderators {
122 if !new_moderators.contains(&&mod_user.moderator.actor_id.clone().into()) {
123 let community_moderator_form = CommunityModeratorForm {
124 community_id: mod_user.community.id,
125 user_id: mod_user.moderator.id,
127 blocking(context.pool(), move |conn| {
128 CommunityModerator::leave(conn, &community_moderator_form)
134 // Add new mods to database which have been added to moderators collection
135 for mod_uri in new_moderators {
136 let mod_user = get_or_fetch_and_upsert_user(&mod_uri, context, request_counter).await?;
137 let current_mod_uris: Vec<DbUrl> = current_moderators
140 .map(|c| c.moderator.actor_id.clone())
142 if !current_mod_uris.contains(&mod_user.actor_id) {
143 let community_moderator_form = CommunityModeratorForm {
144 community_id: community.id,
145 user_id: mod_user.id,
147 blocking(context.pool(), move |conn| {
148 CommunityModerator::join(conn, &community_moderator_form)
158 #[async_trait::async_trait(?Send)]
159 impl FromApubToForm<GroupExt> for CommunityForm {
162 context: &LemmyContext,
163 expected_domain: Option<Url>,
164 request_counter: &mut i32,
165 ) -> Result<Self, LemmyError> {
166 let expected_domain = expected_domain.expect("expected_domain must be set for community");
167 let moderator_uris = fetch_community_mods(context, group, request_counter).await?;
168 let creator_uri = moderator_uris.first().context(location_info!())?;
170 let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
173 .preferred_username()
174 .context(location_info!())?
179 .context(location_info!())?
181 .context(location_info!())?
183 .context(location_info!())?
186 let description = get_source_markdown_value(group)?;
189 check_slurs(&title)?;
190 check_slurs_opt(&description)?;
192 let icon = match group.icon() {
193 Some(any_image) => Some(
194 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
195 .context(location_info!())?
196 .context(location_info!())?
198 .context(location_info!())?
199 .as_single_xsd_any_uri()
200 .map(|u| u.to_owned().into()),
204 let banner = match group.image() {
205 Some(any_image) => Some(
206 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
207 .context(location_info!())?
208 .context(location_info!())?
210 .context(location_info!())?
211 .as_single_xsd_any_uri()
212 .map(|u| u.to_owned().into()),
216 let shared_inbox = group
219 .map(|e| e.shared_inbox)
221 .map(|s| s.to_owned().into());
227 creator_id: creator.id,
229 published: group.inner.published().map(|u| u.to_owned().naive_local()),
230 updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
232 nsfw: group.ext_one.sensitive.unwrap_or(false),
233 actor_id: Some(check_object_domain(group, expected_domain)?),
236 public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
237 last_refreshed_at: Some(naive_now()),
244 .context(location_info!())?
248 inbox_url: Some(group.inner.inbox()?.to_owned().into()),
249 shared_inbox_url: Some(shared_inbox),