2 newtypes::{CommunityId, DbUrl, PersonId},
3 schema::{community, community_follower, instance},
5 actor_language::CommunityLanguage,
12 CommunityModeratorForm,
14 CommunityPersonBanForm,
18 traits::{ApubActor, Bannable, Crud, Followable, Joinable},
19 utils::{functions::lower, get_conn, DbPool},
30 NullableExpressionMethods,
34 use diesel_async::RunQueryDsl;
37 impl Crud for Community {
38 type InsertForm = CommunityInsertForm;
39 type UpdateForm = CommunityUpdateForm;
40 type IdType = CommunityId;
42 async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
43 let is_new_community = match &form.actor_id {
44 Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
47 let conn = &mut get_conn(pool).await?;
49 // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
50 let community_ = insert_into(community::table)
52 .on_conflict(community::actor_id)
55 .get_result::<Self>(conn)
58 // Initialize languages for new community
60 CommunityLanguage::update(pool, vec![], community_.id).await?;
67 pool: &mut DbPool<'_>,
68 community_id: CommunityId,
69 form: &Self::UpdateForm,
70 ) -> Result<Self, Error> {
71 let conn = &mut get_conn(pool).await?;
72 diesel::update(community::table.find(community_id))
74 .get_result::<Self>(conn)
80 impl Joinable for CommunityModerator {
81 type Form = CommunityModeratorForm;
83 pool: &mut DbPool<'_>,
84 community_moderator_form: &CommunityModeratorForm,
85 ) -> Result<Self, Error> {
86 use crate::schema::community_moderator::dsl::community_moderator;
87 let conn = &mut get_conn(pool).await?;
88 insert_into(community_moderator)
89 .values(community_moderator_form)
90 .get_result::<Self>(conn)
95 pool: &mut DbPool<'_>,
96 community_moderator_form: &CommunityModeratorForm,
97 ) -> Result<usize, Error> {
98 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
99 let conn = &mut get_conn(pool).await?;
102 .filter(community_id.eq(community_moderator_form.community_id))
103 .filter(person_id.eq(community_moderator_form.person_id)),
110 pub enum CollectionType {
116 /// Get the community which has a given moderators or featured url, also return the collection type
117 pub async fn get_by_collection_url(
118 pool: &mut DbPool<'_>,
120 ) -> Result<(Community, CollectionType), Error> {
121 use crate::schema::community::dsl::{featured_url, moderators_url};
122 use CollectionType::*;
123 let conn = &mut get_conn(pool).await?;
124 let res = community::table
125 .filter(moderators_url.eq(url))
129 return Ok((c, Moderators));
131 let res = community::table
132 .filter(featured_url.eq(url))
136 return Ok((c, Featured));
138 Err(diesel::NotFound)
142 impl CommunityModerator {
143 pub async fn delete_for_community(
144 pool: &mut DbPool<'_>,
145 for_community_id: CommunityId,
146 ) -> Result<usize, Error> {
147 use crate::schema::community_moderator::dsl::{community_id, community_moderator};
148 let conn = &mut get_conn(pool).await?;
150 diesel::delete(community_moderator.filter(community_id.eq(for_community_id)))
155 pub async fn leave_all_communities(
156 pool: &mut DbPool<'_>,
157 for_person_id: PersonId,
158 ) -> Result<usize, Error> {
159 use crate::schema::community_moderator::dsl::{community_moderator, person_id};
160 let conn = &mut get_conn(pool).await?;
161 diesel::delete(community_moderator.filter(person_id.eq(for_person_id)))
166 pub async fn get_person_moderated_communities(
167 pool: &mut DbPool<'_>,
168 for_person_id: PersonId,
169 ) -> Result<Vec<CommunityId>, Error> {
170 use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
171 let conn = &mut get_conn(pool).await?;
173 .filter(person_id.eq(for_person_id))
174 .select(community_id)
175 .load::<CommunityId>(conn)
181 impl Bannable for CommunityPersonBan {
182 type Form = CommunityPersonBanForm;
184 pool: &mut DbPool<'_>,
185 community_person_ban_form: &CommunityPersonBanForm,
186 ) -> Result<Self, Error> {
187 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
188 let conn = &mut get_conn(pool).await?;
189 insert_into(community_person_ban)
190 .values(community_person_ban_form)
191 .on_conflict((community_id, person_id))
193 .set(community_person_ban_form)
194 .get_result::<Self>(conn)
199 pool: &mut DbPool<'_>,
200 community_person_ban_form: &CommunityPersonBanForm,
201 ) -> Result<usize, Error> {
202 use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
203 let conn = &mut get_conn(pool).await?;
206 .filter(community_id.eq(community_person_ban_form.community_id))
207 .filter(person_id.eq(community_person_ban_form.person_id)),
214 impl CommunityFollower {
215 pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
219 SubscribedType::Pending
221 SubscribedType::Subscribed
224 // If the row doesn't exist, the person isn't a follower.
225 None => SubscribedType::NotSubscribed,
229 pub fn select_subscribed_type() -> dsl::Nullable<community_follower::pending> {
230 community_follower::pending.nullable()
234 impl Queryable<sql_types::Nullable<sql_types::Bool>, Pg> for SubscribedType {
235 type Row = Option<bool>;
236 fn build(row: Self::Row) -> deserialize::Result<Self> {
238 Some(true) => SubscribedType::Pending,
239 Some(false) => SubscribedType::Subscribed,
240 None => SubscribedType::NotSubscribed,
246 impl Followable for CommunityFollower {
247 type Form = CommunityFollowerForm;
248 async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> {
249 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
250 let conn = &mut get_conn(pool).await?;
251 insert_into(community_follower)
253 .on_conflict((community_id, person_id))
256 .get_result::<Self>(conn)
259 async fn follow_accepted(
260 pool: &mut DbPool<'_>,
261 community_id_: CommunityId,
262 person_id_: PersonId,
263 ) -> Result<Self, Error> {
264 use crate::schema::community_follower::dsl::{
270 let conn = &mut get_conn(pool).await?;
273 .filter(community_id.eq(community_id_))
274 .filter(person_id.eq(person_id_)),
276 .set(pending.eq(false))
277 .get_result::<Self>(conn)
280 async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<usize, Error> {
281 use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
282 let conn = &mut get_conn(pool).await?;
285 .filter(community_id.eq(&form.community_id))
286 .filter(person_id.eq(&form.person_id)),
294 impl ApubActor for Community {
295 async fn read_from_apub_id(
296 pool: &mut DbPool<'_>,
298 ) -> Result<Option<Self>, Error> {
299 let conn = &mut get_conn(pool).await?;
302 .filter(community::actor_id.eq(object_id))
303 .first::<Community>(conn)
310 async fn read_from_name(
311 pool: &mut DbPool<'_>,
312 community_name: &str,
313 include_deleted: bool,
314 ) -> Result<Community, Error> {
315 let conn = &mut get_conn(pool).await?;
316 let mut q = community::table
318 .filter(community::local.eq(true))
319 .filter(lower(community::name).eq(community_name.to_lowercase()));
320 if !include_deleted {
322 .filter(community::deleted.eq(false))
323 .filter(community::removed.eq(false));
325 q.first::<Self>(conn).await
328 async fn read_from_name_and_domain(
329 pool: &mut DbPool<'_>,
330 community_name: &str,
332 ) -> Result<Community, Error> {
333 let conn = &mut get_conn(pool).await?;
335 .inner_join(instance::table)
336 .filter(lower(community::name).eq(community_name.to_lowercase()))
337 .filter(instance::domain.eq(for_domain))
338 .select(community::all_columns)
346 #![allow(clippy::unwrap_used)]
347 #![allow(clippy::indexing_slicing)]
354 CommunityFollowerForm,
357 CommunityModeratorForm,
359 CommunityPersonBanForm,
363 person::{Person, PersonInsertForm},
365 traits::{Bannable, Crud, Followable, Joinable},
366 utils::build_db_pool_for_tests,
368 use serial_test::serial;
372 async fn test_crud() {
373 let pool = &build_db_pool_for_tests().await;
374 let pool = &mut pool.into();
376 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
380 let new_person = PersonInsertForm::builder()
381 .name("bobbee".into())
382 .public_key("pubkey".to_string())
383 .instance_id(inserted_instance.id)
386 let inserted_person = Person::create(pool, &new_person).await.unwrap();
388 let new_community = CommunityInsertForm::builder()
390 .title("nada".to_owned())
391 .public_key("pubkey".to_string())
392 .instance_id(inserted_instance.id)
395 let inserted_community = Community::create(pool, &new_community).await.unwrap();
397 let expected_community = Community {
398 id: inserted_community.id,
400 title: "nada".to_owned(),
405 published: inserted_community.published,
407 actor_id: inserted_community.actor_id.clone(),
410 public_key: "pubkey".to_owned(),
411 last_refreshed_at: inserted_community.published,
414 followers_url: inserted_community.followers_url.clone(),
415 inbox_url: inserted_community.inbox_url.clone(),
416 shared_inbox_url: None,
417 moderators_url: None,
420 posting_restricted_to_mods: false,
421 instance_id: inserted_instance.id,
424 let community_follower_form = CommunityFollowerForm {
425 community_id: inserted_community.id,
426 person_id: inserted_person.id,
430 let inserted_community_follower = CommunityFollower::follow(pool, &community_follower_form)
434 let expected_community_follower = CommunityFollower {
435 id: inserted_community_follower.id,
436 community_id: inserted_community.id,
437 person_id: inserted_person.id,
439 published: inserted_community_follower.published,
442 let community_moderator_form = CommunityModeratorForm {
443 community_id: inserted_community.id,
444 person_id: inserted_person.id,
447 let inserted_community_moderator = CommunityModerator::join(pool, &community_moderator_form)
451 let expected_community_moderator = CommunityModerator {
452 id: inserted_community_moderator.id,
453 community_id: inserted_community.id,
454 person_id: inserted_person.id,
455 published: inserted_community_moderator.published,
458 let community_person_ban_form = CommunityPersonBanForm {
459 community_id: inserted_community.id,
460 person_id: inserted_person.id,
464 let inserted_community_person_ban = CommunityPersonBan::ban(pool, &community_person_ban_form)
468 let expected_community_person_ban = CommunityPersonBan {
469 id: inserted_community_person_ban.id,
470 community_id: inserted_community.id,
471 person_id: inserted_person.id,
472 published: inserted_community_person_ban.published,
476 let read_community = Community::read(pool, inserted_community.id).await.unwrap();
478 let update_community_form = CommunityUpdateForm {
479 title: Some("nada".to_owned()),
482 let updated_community = Community::update(pool, inserted_community.id, &update_community_form)
486 let ignored_community = CommunityFollower::unfollow(pool, &community_follower_form)
489 let left_community = CommunityModerator::leave(pool, &community_moderator_form)
492 let unban = CommunityPersonBan::unban(pool, &community_person_ban_form)
495 let num_deleted = Community::delete(pool, inserted_community.id)
498 Person::delete(pool, inserted_person.id).await.unwrap();
499 Instance::delete(pool, inserted_instance.id).await.unwrap();
501 assert_eq!(expected_community, read_community);
502 assert_eq!(expected_community, inserted_community);
503 assert_eq!(expected_community, updated_community);
504 assert_eq!(expected_community_follower, inserted_community_follower);
505 assert_eq!(expected_community_moderator, inserted_community_moderator);
506 assert_eq!(expected_community_person_ban, inserted_community_person_ban);
507 assert_eq!(1, ignored_community);
508 assert_eq!(1, left_community);
509 assert_eq!(1, unban);
510 // assert_eq!(2, loaded_count);
511 assert_eq!(1, num_deleted);