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;
30 async fn read(pool: &mut DbPool<'_>, community_id: CommunityId) -> Result<Self, Error> {
31 let conn = &mut get_conn(pool).await?;
38 async fn delete(pool: &mut DbPool<'_>, community_id: CommunityId) -> Result<usize, Error> {
39 let conn = &mut get_conn(pool).await?;
40 diesel::delete(community::table.find(community_id))
45 async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
46 let is_new_community = match &form.actor_id {
47 Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
50 let conn = &mut get_conn(pool).await?;
52 // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
53 let community_ = insert_into(community::table)
55 .on_conflict(community::actor_id)
58 .get_result::<Self>(conn)
61 // Initialize languages for new community
63 CommunityLanguage::update(pool, vec![], community_.id).await?;
70 pool: &mut DbPool<'_>,
71 community_id: CommunityId,
72 form: &Self::UpdateForm,
73 ) -> Result<Self, Error> {
74 let conn = &mut get_conn(pool).await?;
75 diesel::update(community::table.find(community_id))
77 .get_result::<Self>(conn)
83 impl Joinable for CommunityModerator {
84 type Form = CommunityModeratorForm;
86 pool: &mut DbPool<'_>,
87 community_moderator_form: &CommunityModeratorForm,
88 ) -> Result<Self, Error> {
89 use crate::schema::community_moderator::dsl::community_moderator;
90 let conn = &mut get_conn(pool).await?;
91 insert_into(community_moderator)
92 .values(community_moderator_form)
93 .get_result::<Self>(conn)
98 pool: &mut DbPool<'_>,
99 community_moderator_form: &CommunityModeratorForm,
100 ) -> Result<usize, Error> {
101 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
102 let conn = &mut get_conn(pool).await?;
105 .filter(community_id.eq(community_moderator_form.community_id))
106 .filter(person_id.eq(community_moderator_form.person_id)),
113 pub enum CollectionType {
119 /// Get the community which has a given moderators or featured url, also return the collection type
120 pub async fn get_by_collection_url(
121 pool: &mut DbPool<'_>,
123 ) -> Result<(Community, CollectionType), Error> {
124 use crate::schema::community::dsl::{featured_url, moderators_url};
125 use CollectionType::*;
126 let conn = &mut get_conn(pool).await?;
127 let res = community::table
128 .filter(moderators_url.eq(url))
132 return Ok((c, Moderators));
134 let res = community::table
135 .filter(featured_url.eq(url))
139 return Ok((c, Featured));
141 Err(diesel::NotFound)
145 impl CommunityModerator {
146 pub async fn delete_for_community(
147 pool: &mut DbPool<'_>,
148 for_community_id: CommunityId,
149 ) -> Result<usize, Error> {
150 use crate::schema::community_moderator::dsl::{community_id, community_moderator};
151 let conn = &mut get_conn(pool).await?;
153 diesel::delete(community_moderator.filter(community_id.eq(for_community_id)))
158 pub async fn leave_all_communities(
159 pool: &mut DbPool<'_>,
160 for_person_id: PersonId,
161 ) -> Result<usize, Error> {
162 use crate::schema::community_moderator::dsl::{community_moderator, person_id};
163 let conn = &mut get_conn(pool).await?;
164 diesel::delete(community_moderator.filter(person_id.eq(for_person_id)))
169 pub async fn get_person_moderated_communities(
170 pool: &mut DbPool<'_>,
171 for_person_id: PersonId,
172 ) -> Result<Vec<CommunityId>, Error> {
173 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
174 let conn = &mut get_conn(pool).await?;
176 .filter(person_id.eq(for_person_id))
177 .select(community_id)
178 .load::<CommunityId>(conn)
184 impl Bannable for CommunityPersonBan {
185 type Form = CommunityPersonBanForm;
187 pool: &mut DbPool<'_>,
188 community_person_ban_form: &CommunityPersonBanForm,
189 ) -> Result<Self, Error> {
190 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
191 let conn = &mut get_conn(pool).await?;
192 insert_into(community_person_ban)
193 .values(community_person_ban_form)
194 .on_conflict((community_id, person_id))
196 .set(community_person_ban_form)
197 .get_result::<Self>(conn)
202 pool: &mut DbPool<'_>,
203 community_person_ban_form: &CommunityPersonBanForm,
204 ) -> Result<usize, Error> {
205 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
206 let conn = &mut get_conn(pool).await?;
209 .filter(community_id.eq(community_person_ban_form.community_id))
210 .filter(person_id.eq(community_person_ban_form.person_id)),
217 impl CommunityFollower {
218 pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
222 SubscribedType::Pending
224 SubscribedType::Subscribed
227 // If the row doesn't exist, the person isn't a follower.
228 None => SubscribedType::NotSubscribed,
234 impl Followable for CommunityFollower {
235 type Form = CommunityFollowerForm;
236 async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> {
237 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
238 let conn = &mut get_conn(pool).await?;
239 insert_into(community_follower)
241 .on_conflict((community_id, person_id))
244 .get_result::<Self>(conn)
247 async fn follow_accepted(
248 pool: &mut DbPool<'_>,
249 community_id_: CommunityId,
250 person_id_: PersonId,
251 ) -> Result<Self, Error> {
252 use crate::schema::community_follower::dsl::{
258 let conn = &mut get_conn(pool).await?;
261 .filter(community_id.eq(community_id_))
262 .filter(person_id.eq(person_id_)),
264 .set(pending.eq(false))
265 .get_result::<Self>(conn)
268 async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<usize, Error> {
269 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
270 let conn = &mut get_conn(pool).await?;
273 .filter(community_id.eq(&form.community_id))
274 .filter(person_id.eq(&form.person_id)),
282 impl ApubActor for Community {
283 async fn read_from_apub_id(
284 pool: &mut DbPool<'_>,
286 ) -> Result<Option<Self>, Error> {
287 let conn = &mut get_conn(pool).await?;
290 .filter(community::actor_id.eq(object_id))
291 .first::<Community>(conn)
298 async fn read_from_name(
299 pool: &mut DbPool<'_>,
300 community_name: &str,
301 include_deleted: bool,
302 ) -> Result<Community, Error> {
303 let conn = &mut get_conn(pool).await?;
304 let mut q = community::table
306 .filter(community::local.eq(true))
307 .filter(lower(community::name).eq(community_name.to_lowercase()));
308 if !include_deleted {
310 .filter(community::deleted.eq(false))
311 .filter(community::removed.eq(false));
313 q.first::<Self>(conn).await
316 async fn read_from_name_and_domain(
317 pool: &mut DbPool<'_>,
318 community_name: &str,
320 ) -> Result<Community, Error> {
321 let conn = &mut get_conn(pool).await?;
323 .inner_join(instance::table)
324 .filter(lower(community::name).eq(community_name.to_lowercase()))
325 .filter(instance::domain.eq(for_domain))
326 .select(community::all_columns)
334 #![allow(clippy::unwrap_used)]
335 #![allow(clippy::indexing_slicing)]
342 CommunityFollowerForm,
345 CommunityModeratorForm,
347 CommunityPersonBanForm,
351 person::{Person, PersonInsertForm},
353 traits::{Bannable, Crud, Followable, Joinable},
354 utils::build_db_pool_for_tests,
356 use serial_test::serial;
360 async fn test_crud() {
361 let pool = &build_db_pool_for_tests().await;
362 let pool = &mut pool.into();
364 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
368 let new_person = PersonInsertForm::builder()
369 .name("bobbee".into())
370 .public_key("pubkey".to_string())
371 .instance_id(inserted_instance.id)
374 let inserted_person = Person::create(pool, &new_person).await.unwrap();
376 let new_community = CommunityInsertForm::builder()
378 .title("nada".to_owned())
379 .public_key("pubkey".to_string())
380 .instance_id(inserted_instance.id)
383 let inserted_community = Community::create(pool, &new_community).await.unwrap();
385 let expected_community = Community {
386 id: inserted_community.id,
388 title: "nada".to_owned(),
393 published: inserted_community.published,
395 actor_id: inserted_community.actor_id.clone(),
398 public_key: "pubkey".to_owned(),
399 last_refreshed_at: inserted_community.published,
402 followers_url: inserted_community.followers_url.clone(),
403 inbox_url: inserted_community.inbox_url.clone(),
404 shared_inbox_url: None,
405 moderators_url: None,
408 posting_restricted_to_mods: false,
409 instance_id: inserted_instance.id,
412 let community_follower_form = CommunityFollowerForm {
413 community_id: inserted_community.id,
414 person_id: inserted_person.id,
418 let inserted_community_follower = CommunityFollower::follow(pool, &community_follower_form)
422 let expected_community_follower = CommunityFollower {
423 id: inserted_community_follower.id,
424 community_id: inserted_community.id,
425 person_id: inserted_person.id,
427 published: inserted_community_follower.published,
430 let community_moderator_form = CommunityModeratorForm {
431 community_id: inserted_community.id,
432 person_id: inserted_person.id,
435 let inserted_community_moderator = CommunityModerator::join(pool, &community_moderator_form)
439 let expected_community_moderator = CommunityModerator {
440 id: inserted_community_moderator.id,
441 community_id: inserted_community.id,
442 person_id: inserted_person.id,
443 published: inserted_community_moderator.published,
446 let community_person_ban_form = CommunityPersonBanForm {
447 community_id: inserted_community.id,
448 person_id: inserted_person.id,
452 let inserted_community_person_ban = CommunityPersonBan::ban(pool, &community_person_ban_form)
456 let expected_community_person_ban = CommunityPersonBan {
457 id: inserted_community_person_ban.id,
458 community_id: inserted_community.id,
459 person_id: inserted_person.id,
460 published: inserted_community_person_ban.published,
464 let read_community = Community::read(pool, inserted_community.id).await.unwrap();
466 let update_community_form = CommunityUpdateForm::builder()
467 .title(Some("nada".to_owned()))
469 let updated_community = Community::update(pool, inserted_community.id, &update_community_form)
473 let ignored_community = CommunityFollower::unfollow(pool, &community_follower_form)
476 let left_community = CommunityModerator::leave(pool, &community_moderator_form)
479 let unban = CommunityPersonBan::unban(pool, &community_person_ban_form)
482 let num_deleted = Community::delete(pool, inserted_community.id)
485 Person::delete(pool, inserted_person.id).await.unwrap();
486 Instance::delete(pool, inserted_instance.id).await.unwrap();
488 assert_eq!(expected_community, read_community);
489 assert_eq!(expected_community, inserted_community);
490 assert_eq!(expected_community, updated_community);
491 assert_eq!(expected_community_follower, inserted_community_follower);
492 assert_eq!(expected_community_moderator, inserted_community_moderator);
493 assert_eq!(expected_community_person_ban, inserted_community_person_ban);
494 assert_eq!(1, ignored_community);
495 assert_eq!(1, left_community);
496 assert_eq!(1, unban);
497 // assert_eq!(2, loaded_count);
498 assert_eq!(1, num_deleted);