2 extensions::{context::lemmy_context, group_extensions::GroupExtension},
3 fetcher::user::get_or_fetch_and_upsert_user,
8 get_source_markdown_value,
9 set_content_and_source,
17 use activitystreams::{
18 actor::{kind::GroupType, ApActor, Endpoints, Group},
20 object::{ApObject, Image, Tombstone},
23 use activitystreams_ext::Ext2;
25 use lemmy_api_structs::blocking;
26 use lemmy_db_queries::{DbPool, Joinable};
27 use lemmy_db_schema::{
29 source::community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
32 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
35 utils::{check_slurs, check_slurs_opt, convert_datetime},
38 use lemmy_websocket::LemmyContext;
41 #[async_trait::async_trait(?Send)]
42 impl ToApub for Community {
43 type ApubType = GroupExt;
45 async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
46 // The attributed to, is an ordered vector with the creator actor_ids first,
47 // then the rest of the moderators
48 // TODO Technically the instance admins can mod the community, but lets
49 // ignore that for now
51 let moderators = blocking(pool, move |conn| {
52 CommunityModeratorView::for_community(&conn, id)
56 let mut group = ApObject::new(Group::new());
58 .set_many_contexts(lemmy_context()?)
59 .set_id(self.actor_id.to_owned().into())
60 .set_name(self.title.to_owned())
61 .set_published(convert_datetime(self.published));
63 if let Some(u) = self.updated.to_owned() {
64 group.set_updated(convert_datetime(u));
66 if let Some(d) = self.description.to_owned() {
67 set_content_and_source(&mut group, &d)?;
70 if let Some(icon_url) = &self.icon {
71 let mut image = Image::new();
72 image.set_url::<Url>(icon_url.to_owned().into());
73 group.set_icon(image.into_any_base()?);
76 if let Some(banner_url) = &self.banner {
77 let mut image = Image::new();
78 image.set_url::<Url>(banner_url.to_owned().into());
79 group.set_image(image.into_any_base()?);
82 let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group);
84 .set_preferred_username(self.name.to_owned())
85 .set_outbox(self.get_outbox_url()?)
86 .set_followers(self.followers_url.clone().into())
87 .set_endpoints(Endpoints {
88 shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
92 let moderators: Vec<Url> = moderators
94 .map(|m| m.moderator.actor_id.into_inner())
99 GroupExtension::new(self.nsfw, moderators)?,
100 self.get_public_key_ext()?,
104 fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
107 self.actor_id.to_owned().into(),
114 #[async_trait::async_trait(?Send)]
115 impl FromApub for Community {
116 type ApubType = GroupExt;
118 /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
121 context: &LemmyContext,
122 expected_domain: Url,
123 request_counter: &mut i32,
124 ) -> Result<Community, LemmyError> {
125 let community: Community =
126 get_object_from_apub(group, context, expected_domain, request_counter).await?;
128 let new_moderators = get_community_moderators(group)?;
129 let community_id = community.id;
130 let current_moderators = blocking(context.pool(), move |conn| {
131 CommunityModeratorView::for_community(&conn, community_id)
134 // Remove old mods from database which arent in the moderators collection anymore
135 for mod_user in ¤t_moderators {
136 if !new_moderators.contains(&&mod_user.moderator.actor_id.clone().into()) {
137 let community_moderator_form = CommunityModeratorForm {
138 community_id: mod_user.community.id,
139 user_id: mod_user.moderator.id,
141 blocking(context.pool(), move |conn| {
142 CommunityModerator::leave(conn, &community_moderator_form)
148 // Add new mods to database which have been added to moderators collection
149 for mod_uri in new_moderators {
150 let mod_user = get_or_fetch_and_upsert_user(&mod_uri, context, request_counter).await?;
151 let current_mod_uris: Vec<DbUrl> = current_moderators
154 .map(|c| c.moderator.actor_id.clone())
156 if !current_mod_uris.contains(&mod_user.actor_id) {
157 let community_moderator_form = CommunityModeratorForm {
158 community_id: community.id,
159 user_id: mod_user.id,
161 blocking(context.pool(), move |conn| {
162 CommunityModerator::join(conn, &community_moderator_form)
172 #[async_trait::async_trait(?Send)]
173 impl FromApubToForm<GroupExt> for CommunityForm {
176 context: &LemmyContext,
177 expected_domain: Url,
178 request_counter: &mut i32,
179 ) -> Result<Self, LemmyError> {
180 let moderator_uris = get_community_moderators(group)?;
181 let creator_uri = moderator_uris.first().context(location_info!())?;
183 let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
186 .preferred_username()
187 .context(location_info!())?
192 .context(location_info!())?
194 .context(location_info!())?
196 .context(location_info!())?
199 let description = get_source_markdown_value(group)?;
202 check_slurs(&title)?;
203 check_slurs_opt(&description)?;
205 let icon = match group.icon() {
206 Some(any_image) => Some(
207 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
208 .context(location_info!())?
209 .context(location_info!())?
211 .context(location_info!())?
212 .as_single_xsd_any_uri()
213 .map(|u| u.to_owned().into()),
217 let banner = match group.image() {
218 Some(any_image) => Some(
219 Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
220 .context(location_info!())?
221 .context(location_info!())?
223 .context(location_info!())?
224 .as_single_xsd_any_uri()
225 .map(|u| u.to_owned().into()),
229 let shared_inbox = group
232 .map(|e| e.shared_inbox)
234 .map(|s| s.to_owned().into());
240 creator_id: creator.id,
242 published: group.inner.published().map(|u| u.to_owned().naive_local()),
243 updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
245 nsfw: group.ext_one.sensitive.unwrap_or(false),
246 actor_id: Some(check_object_domain(group, expected_domain)?),
249 public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
250 last_refreshed_at: Some(naive_now()),
257 .context(location_info!())?
261 inbox_url: Some(group.inner.inbox()?.to_owned().into()),
262 shared_inbox_url: Some(shared_inbox),
267 fn get_community_moderators(group: &GroupExt) -> Result<Vec<&Url>, LemmyError> {
268 if let Some(moderators) = &group.ext_one.moderators {
272 .map(|i| i.as_many())
274 .context(location_info!())?
276 .filter_map(|i| i.as_xsd_any_uri())