2 newtypes::{CommunityId, DbUrl, PersonId},
3 schema::{community, instance},
5 actor_language::CommunityLanguage,
12 CommunityModeratorForm,
14 CommunityPersonBanForm,
18 traits::{ApubActor, Bannable, Crud, Followable, Joinable},
19 utils::{functions::lower, get_conn, DbPool},
22 use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
23 use diesel_async::RunQueryDsl;
26 impl Crud for Community {
27 type InsertForm = CommunityInsertForm;
28 type UpdateForm = CommunityUpdateForm;
29 type IdType = CommunityId;
31 async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
32 let is_new_community = match &form.actor_id {
33 Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
36 let conn = &mut get_conn(pool).await?;
38 // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
39 let community_ = insert_into(community::table)
41 .on_conflict(community::actor_id)
44 .get_result::<Self>(conn)
47 // Initialize languages for new community
49 CommunityLanguage::update(pool, vec![], community_.id).await?;
56 pool: &mut DbPool<'_>,
57 community_id: CommunityId,
58 form: &Self::UpdateForm,
59 ) -> Result<Self, Error> {
60 let conn = &mut get_conn(pool).await?;
61 diesel::update(community::table.find(community_id))
63 .get_result::<Self>(conn)
69 impl Joinable for CommunityModerator {
70 type Form = CommunityModeratorForm;
72 pool: &mut DbPool<'_>,
73 community_moderator_form: &CommunityModeratorForm,
74 ) -> Result<Self, Error> {
75 use crate::schema::community_moderator::dsl::community_moderator;
76 let conn = &mut get_conn(pool).await?;
77 insert_into(community_moderator)
78 .values(community_moderator_form)
79 .get_result::<Self>(conn)
84 pool: &mut DbPool<'_>,
85 community_moderator_form: &CommunityModeratorForm,
86 ) -> Result<usize, Error> {
87 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
88 let conn = &mut get_conn(pool).await?;
91 .filter(community_id.eq(community_moderator_form.community_id))
92 .filter(person_id.eq(community_moderator_form.person_id)),
99 pub enum CollectionType {
105 /// Get the community which has a given moderators or featured url, also return the collection type
106 pub async fn get_by_collection_url(
107 pool: &mut DbPool<'_>,
109 ) -> Result<(Community, CollectionType), Error> {
110 use crate::schema::community::dsl::{featured_url, moderators_url};
111 use CollectionType::*;
112 let conn = &mut get_conn(pool).await?;
113 let res = community::table
114 .filter(moderators_url.eq(url))
118 return Ok((c, Moderators));
120 let res = community::table
121 .filter(featured_url.eq(url))
125 return Ok((c, Featured));
127 Err(diesel::NotFound)
131 impl CommunityModerator {
132 pub async fn delete_for_community(
133 pool: &mut DbPool<'_>,
134 for_community_id: CommunityId,
135 ) -> Result<usize, Error> {
136 use crate::schema::community_moderator::dsl::{community_id, community_moderator};
137 let conn = &mut get_conn(pool).await?;
139 diesel::delete(community_moderator.filter(community_id.eq(for_community_id)))
144 pub async fn leave_all_communities(
145 pool: &mut DbPool<'_>,
146 for_person_id: PersonId,
147 ) -> Result<usize, Error> {
148 use crate::schema::community_moderator::dsl::{community_moderator, person_id};
149 let conn = &mut get_conn(pool).await?;
150 diesel::delete(community_moderator.filter(person_id.eq(for_person_id)))
155 pub async fn get_person_moderated_communities(
156 pool: &mut DbPool<'_>,
157 for_person_id: PersonId,
158 ) -> Result<Vec<CommunityId>, Error> {
159 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
160 let conn = &mut get_conn(pool).await?;
162 .filter(person_id.eq(for_person_id))
163 .select(community_id)
164 .load::<CommunityId>(conn)
170 impl Bannable for CommunityPersonBan {
171 type Form = CommunityPersonBanForm;
173 pool: &mut DbPool<'_>,
174 community_person_ban_form: &CommunityPersonBanForm,
175 ) -> Result<Self, Error> {
176 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
177 let conn = &mut get_conn(pool).await?;
178 insert_into(community_person_ban)
179 .values(community_person_ban_form)
180 .on_conflict((community_id, person_id))
182 .set(community_person_ban_form)
183 .get_result::<Self>(conn)
188 pool: &mut DbPool<'_>,
189 community_person_ban_form: &CommunityPersonBanForm,
190 ) -> Result<usize, Error> {
191 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
192 let conn = &mut get_conn(pool).await?;
195 .filter(community_id.eq(community_person_ban_form.community_id))
196 .filter(person_id.eq(community_person_ban_form.person_id)),
203 impl CommunityFollower {
204 pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
208 SubscribedType::Pending
210 SubscribedType::Subscribed
213 // If the row doesn't exist, the person isn't a follower.
214 None => SubscribedType::NotSubscribed,
220 impl Followable for CommunityFollower {
221 type Form = CommunityFollowerForm;
222 async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> {
223 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
224 let conn = &mut get_conn(pool).await?;
225 insert_into(community_follower)
227 .on_conflict((community_id, person_id))
230 .get_result::<Self>(conn)
233 async fn follow_accepted(
234 pool: &mut DbPool<'_>,
235 community_id_: CommunityId,
236 person_id_: PersonId,
237 ) -> Result<Self, Error> {
238 use crate::schema::community_follower::dsl::{
244 let conn = &mut get_conn(pool).await?;
247 .filter(community_id.eq(community_id_))
248 .filter(person_id.eq(person_id_)),
250 .set(pending.eq(false))
251 .get_result::<Self>(conn)
254 async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<usize, Error> {
255 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
256 let conn = &mut get_conn(pool).await?;
259 .filter(community_id.eq(&form.community_id))
260 .filter(person_id.eq(&form.person_id)),
268 impl ApubActor for Community {
269 async fn read_from_apub_id(
270 pool: &mut DbPool<'_>,
272 ) -> Result<Option<Self>, Error> {
273 let conn = &mut get_conn(pool).await?;
276 .filter(community::actor_id.eq(object_id))
277 .first::<Community>(conn)
284 async fn read_from_name(
285 pool: &mut DbPool<'_>,
286 community_name: &str,
287 include_deleted: bool,
288 ) -> Result<Community, Error> {
289 let conn = &mut get_conn(pool).await?;
290 let mut q = community::table
292 .filter(community::local.eq(true))
293 .filter(lower(community::name).eq(community_name.to_lowercase()));
294 if !include_deleted {
296 .filter(community::deleted.eq(false))
297 .filter(community::removed.eq(false));
299 q.first::<Self>(conn).await
302 async fn read_from_name_and_domain(
303 pool: &mut DbPool<'_>,
304 community_name: &str,
306 ) -> Result<Community, Error> {
307 let conn = &mut get_conn(pool).await?;
309 .inner_join(instance::table)
310 .filter(lower(community::name).eq(community_name.to_lowercase()))
311 .filter(instance::domain.eq(for_domain))
312 .select(community::all_columns)
320 #![allow(clippy::unwrap_used)]
321 #![allow(clippy::indexing_slicing)]
328 CommunityFollowerForm,
331 CommunityModeratorForm,
333 CommunityPersonBanForm,
337 person::{Person, PersonInsertForm},
339 traits::{Bannable, Crud, Followable, Joinable},
340 utils::build_db_pool_for_tests,
342 use serial_test::serial;
346 async fn test_crud() {
347 let pool = &build_db_pool_for_tests().await;
348 let pool = &mut pool.into();
350 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
354 let new_person = PersonInsertForm::builder()
355 .name("bobbee".into())
356 .public_key("pubkey".to_string())
357 .instance_id(inserted_instance.id)
360 let inserted_person = Person::create(pool, &new_person).await.unwrap();
362 let new_community = CommunityInsertForm::builder()
364 .title("nada".to_owned())
365 .public_key("pubkey".to_string())
366 .instance_id(inserted_instance.id)
369 let inserted_community = Community::create(pool, &new_community).await.unwrap();
371 let expected_community = Community {
372 id: inserted_community.id,
374 title: "nada".to_owned(),
379 published: inserted_community.published,
381 actor_id: inserted_community.actor_id.clone(),
384 public_key: "pubkey".to_owned(),
385 last_refreshed_at: inserted_community.published,
388 followers_url: inserted_community.followers_url.clone(),
389 inbox_url: inserted_community.inbox_url.clone(),
390 shared_inbox_url: None,
391 moderators_url: None,
394 posting_restricted_to_mods: false,
395 instance_id: inserted_instance.id,
398 let community_follower_form = CommunityFollowerForm {
399 community_id: inserted_community.id,
400 person_id: inserted_person.id,
404 let inserted_community_follower = CommunityFollower::follow(pool, &community_follower_form)
408 let expected_community_follower = CommunityFollower {
409 id: inserted_community_follower.id,
410 community_id: inserted_community.id,
411 person_id: inserted_person.id,
413 published: inserted_community_follower.published,
416 let community_moderator_form = CommunityModeratorForm {
417 community_id: inserted_community.id,
418 person_id: inserted_person.id,
421 let inserted_community_moderator = CommunityModerator::join(pool, &community_moderator_form)
425 let expected_community_moderator = CommunityModerator {
426 id: inserted_community_moderator.id,
427 community_id: inserted_community.id,
428 person_id: inserted_person.id,
429 published: inserted_community_moderator.published,
432 let community_person_ban_form = CommunityPersonBanForm {
433 community_id: inserted_community.id,
434 person_id: inserted_person.id,
438 let inserted_community_person_ban = CommunityPersonBan::ban(pool, &community_person_ban_form)
442 let expected_community_person_ban = CommunityPersonBan {
443 id: inserted_community_person_ban.id,
444 community_id: inserted_community.id,
445 person_id: inserted_person.id,
446 published: inserted_community_person_ban.published,
450 let read_community = Community::read(pool, inserted_community.id).await.unwrap();
452 let update_community_form = CommunityUpdateForm::builder()
453 .title(Some("nada".to_owned()))
455 let updated_community = Community::update(pool, inserted_community.id, &update_community_form)
459 let ignored_community = CommunityFollower::unfollow(pool, &community_follower_form)
462 let left_community = CommunityModerator::leave(pool, &community_moderator_form)
465 let unban = CommunityPersonBan::unban(pool, &community_person_ban_form)
468 let num_deleted = Community::delete(pool, inserted_community.id)
471 Person::delete(pool, inserted_person.id).await.unwrap();
472 Instance::delete(pool, inserted_instance.id).await.unwrap();
474 assert_eq!(expected_community, read_community);
475 assert_eq!(expected_community, inserted_community);
476 assert_eq!(expected_community, updated_community);
477 assert_eq!(expected_community_follower, inserted_community_follower);
478 assert_eq!(expected_community_moderator, inserted_community_moderator);
479 assert_eq!(expected_community_person_ban, inserted_community_person_ban);
480 assert_eq!(1, ignored_community);
481 assert_eq!(1, left_community);
482 assert_eq!(1, unban);
483 // assert_eq!(2, loaded_count);
484 assert_eq!(1, num_deleted);