]> Untitled Git - lemmy.git/blobdiff - crates/db_schema/src/impls/community.rs
implement language tags for site/community in db and api (#2434)
[lemmy.git] / crates / db_schema / src / impls / community.rs
index 8acce790cd3f7ff50efab572aeea70c03d01c441..966761b2d8f3c0611bb98630502f7e76ae3b2f2e 100644 (file)
@@ -1,24 +1,32 @@
 use crate::{
-  naive_now,
   newtypes::{CommunityId, DbUrl, PersonId},
-  source::community::{
-    Community,
-    CommunityFollower,
-    CommunityFollowerForm,
-    CommunityForm,
-    CommunityModerator,
-    CommunityModeratorForm,
-    CommunityPersonBan,
-    CommunityPersonBanForm,
-    CommunitySafe,
+  source::{
+    actor_language::{CommunityLanguage, SiteLanguage},
+    community::{
+      Community,
+      CommunityFollower,
+      CommunityFollowerForm,
+      CommunityForm,
+      CommunityModerator,
+      CommunityModeratorForm,
+      CommunityPersonBan,
+      CommunityPersonBanForm,
+      CommunitySafe,
+    },
   },
-  traits::{Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
+  traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
+  utils::{functions::lower, naive_now},
+  SubscribedType,
+};
+use diesel::{
+  dsl::*,
+  result::Error,
+  ExpressionMethods,
+  PgConnection,
+  QueryDsl,
+  RunQueryDsl,
+  TextExpressionMethods,
 };
-use chrono::NaiveDateTime;
-use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
-use lemmy_apub_lib::traits::{ActorType, ApubObject};
-use lemmy_utils::LemmyError;
-use url::Url;
 
 mod safe_type {
   use crate::{schema::community::*, source::community::Community, traits::ToSafe};
@@ -37,6 +45,8 @@ mod safe_type {
     local,
     icon,
     banner,
+    hidden,
+    posting_restricted_to_mods,
   );
 
   impl ToSafe for Community {
@@ -56,6 +66,8 @@ mod safe_type {
         local,
         icon,
         banner,
+        hidden,
+        posting_restricted_to_mods,
       )
     }
   }
@@ -64,25 +76,36 @@ mod safe_type {
 impl Crud for Community {
   type Form = CommunityForm;
   type IdType = CommunityId;
-  fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
+  fn read(conn: &mut PgConnection, community_id: CommunityId) -> Result<Self, Error> {
     use crate::schema::community::dsl::*;
     community.find(community_id).first::<Self>(conn)
   }
 
-  fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> {
+  fn delete(conn: &mut PgConnection, community_id: CommunityId) -> Result<usize, Error> {
     use crate::schema::community::dsl::*;
     diesel::delete(community.find(community_id)).execute(conn)
   }
 
-  fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
+  fn create(conn: &mut PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
     use crate::schema::community::dsl::*;
-    insert_into(community)
+    let community_ = insert_into(community)
       .values(new_community)
-      .get_result::<Self>(conn)
+      .get_result::<Self>(conn)?;
+
+    let site_languages = SiteLanguage::read_local(conn);
+    if let Ok(langs) = site_languages {
+      // if site exists, init user with site languages
+      CommunityLanguage::update(conn, langs, community_.id)?;
+    } else {
+      // otherwise, init with all languages (this only happens during tests)
+      CommunityLanguage::update(conn, vec![], community_.id)?;
+    }
+
+    Ok(community_)
   }
 
   fn update(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_id: CommunityId,
     new_community: &CommunityForm,
   ) -> Result<Self, Error> {
@@ -94,16 +117,8 @@ impl Crud for Community {
 }
 
 impl Community {
-  pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
-    use crate::schema::community::dsl::*;
-    community
-      .filter(local.eq(true))
-      .filter(name.eq(community_name))
-      .first::<Self>(conn)
-  }
-
   pub fn update_deleted(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_id: CommunityId,
     new_deleted: bool,
   ) -> Result<Community, Error> {
@@ -114,7 +129,7 @@ impl Community {
   }
 
   pub fn update_removed(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_id: CommunityId,
     new_removed: bool,
   ) -> Result<Community, Error> {
@@ -124,22 +139,15 @@ impl Community {
       .get_result::<Self>(conn)
   }
 
-  pub fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error> {
+  pub fn distinct_federated_communities(conn: &mut PgConnection) -> Result<Vec<DbUrl>, Error> {
     use crate::schema::community::dsl::*;
-    community.select(actor_id).distinct().load::<String>(conn)
+    community.select(actor_id).distinct().load::<DbUrl>(conn)
   }
 
-  pub fn read_from_followers_url(
-    conn: &PgConnection,
-    followers_url_: &DbUrl,
+  pub fn upsert(
+    conn: &mut PgConnection,
+    community_form: &CommunityForm,
   ) -> Result<Community, Error> {
-    use crate::schema::community::dsl::*;
-    community
-      .filter(followers_url.eq(followers_url_))
-      .first::<Self>(conn)
-  }
-
-  pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
     use crate::schema::community::dsl::*;
     insert_into(community)
       .values(community_form)
@@ -148,12 +156,25 @@ impl Community {
       .set(community_form)
       .get_result::<Self>(conn)
   }
+
+  pub fn remove_avatar_and_banner(
+    conn: &mut PgConnection,
+    community_id: CommunityId,
+  ) -> Result<Self, Error> {
+    use crate::schema::community::dsl::*;
+    diesel::update(community.find(community_id))
+      .set((
+        icon.eq::<Option<String>>(None),
+        banner.eq::<Option<String>>(None),
+      ))
+      .get_result::<Self>(conn)
+  }
 }
 
 impl Joinable for CommunityModerator {
   type Form = CommunityModeratorForm;
   fn join(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_moderator_form: &CommunityModeratorForm,
   ) -> Result<Self, Error> {
     use crate::schema::community_moderator::dsl::*;
@@ -163,7 +184,7 @@ impl Joinable for CommunityModerator {
   }
 
   fn leave(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_moderator_form: &CommunityModeratorForm,
   ) -> Result<usize, Error> {
     use crate::schema::community_moderator::dsl::*;
@@ -198,7 +219,7 @@ impl DeleteableOrRemoveable for Community {
 
 impl CommunityModerator {
   pub fn delete_for_community(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     for_community_id: CommunityId,
   ) -> Result<usize, Error> {
     use crate::schema::community_moderator::dsl::*;
@@ -206,7 +227,7 @@ impl CommunityModerator {
   }
 
   pub fn get_person_moderated_communities(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     for_person_id: PersonId,
   ) -> Result<Vec<CommunityId>, Error> {
     use crate::schema::community_moderator::dsl::*;
@@ -220,17 +241,20 @@ impl CommunityModerator {
 impl Bannable for CommunityPersonBan {
   type Form = CommunityPersonBanForm;
   fn ban(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_person_ban_form: &CommunityPersonBanForm,
   ) -> Result<Self, Error> {
     use crate::schema::community_person_ban::dsl::*;
     insert_into(community_person_ban)
       .values(community_person_ban_form)
+      .on_conflict((community_id, person_id))
+      .do_update()
+      .set(community_person_ban_form)
       .get_result::<Self>(conn)
   }
 
   fn unban(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_person_ban_form: &CommunityPersonBanForm,
   ) -> Result<usize, Error> {
     use crate::schema::community_person_ban::dsl::*;
@@ -243,10 +267,26 @@ impl Bannable for CommunityPersonBan {
   }
 }
 
+impl CommunityFollower {
+  pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
+    match follower {
+      Some(f) => {
+        if f.pending.unwrap_or(false) {
+          SubscribedType::Pending
+        } else {
+          SubscribedType::Subscribed
+        }
+      }
+      // If the row doesn't exist, the person isn't a follower.
+      None => SubscribedType::NotSubscribed,
+    }
+  }
+}
+
 impl Followable for CommunityFollower {
   type Form = CommunityFollowerForm;
   fn follow(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_follower_form: &CommunityFollowerForm,
   ) -> Result<Self, Error> {
     use crate::schema::community_follower::dsl::*;
@@ -258,7 +298,7 @@ impl Followable for CommunityFollower {
       .get_result::<Self>(conn)
   }
   fn follow_accepted(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_id_: CommunityId,
     person_id_: PersonId,
   ) -> Result<Self, Error>
@@ -271,11 +311,11 @@ impl Followable for CommunityFollower {
         .filter(community_id.eq(community_id_))
         .filter(person_id.eq(person_id_)),
     )
-    .set(pending.eq(true))
+    .set(pending.eq(false))
     .get_result::<Self>(conn)
   }
   fn unfollow(
-    conn: &PgConnection,
+    conn: &mut PgConnection,
     community_follower_form: &CommunityFollowerForm,
   ) -> Result<usize, Error> {
     use crate::schema::community_follower::dsl::*;
@@ -288,7 +328,10 @@ impl Followable for CommunityFollower {
   }
   // TODO: this function name only makes sense if you call it with a remote community. for a local
   //       community, it will also return true if only remote followers exist
-  fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
+  fn has_local_followers(
+    conn: &mut PgConnection,
+    community_id_: CommunityId,
+  ) -> Result<bool, Error> {
     use crate::schema::community_follower::dsl::*;
     diesel::select(exists(
       community_follower.filter(community_id.eq(community_id_)),
@@ -297,87 +340,77 @@ impl Followable for CommunityFollower {
   }
 }
 
-impl ApubObject for Community {
-  type DataType = PgConnection;
-
-  fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
-    Some(self.last_refreshed_at)
-  }
-
-  fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, LemmyError> {
+impl ApubActor for Community {
+  fn read_from_apub_id(conn: &mut PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
     use crate::schema::community::dsl::*;
-    let object_id: DbUrl = object_id.into();
     Ok(
       community
         .filter(actor_id.eq(object_id))
-        .first::<Self>(conn)
-        .ok(),
+        .first::<Community>(conn)
+        .ok()
+        .map(Into::into),
     )
   }
 
-  fn delete(self, conn: &PgConnection) -> Result<(), LemmyError> {
+  fn read_from_name(
+    conn: &mut PgConnection,
+    community_name: &str,
+    include_deleted: bool,
+  ) -> Result<Community, Error> {
     use crate::schema::community::dsl::*;
-    diesel::update(community.find(self.id))
-      .set((deleted.eq(true), updated.eq(naive_now())))
-      .get_result::<Self>(conn)?;
-    Ok(())
-  }
-}
-
-impl ActorType for Community {
-  fn is_local(&self) -> bool {
-    self.local
-  }
-  fn actor_id(&self) -> Url {
-    self.actor_id.to_owned().into()
-  }
-  fn name(&self) -> String {
-    self.name.clone()
-  }
-  fn public_key(&self) -> Option<String> {
-    self.public_key.to_owned()
-  }
-  fn private_key(&self) -> Option<String> {
-    self.private_key.to_owned()
-  }
-
-  fn inbox_url(&self) -> Url {
-    self.inbox_url.clone().into()
+    let mut q = community
+      .into_boxed()
+      .filter(local.eq(true))
+      .filter(lower(name).eq(lower(community_name)));
+    if !include_deleted {
+      q = q.filter(deleted.eq(false)).filter(removed.eq(false));
+    }
+    q.first::<Self>(conn)
   }
 
-  fn shared_inbox_url(&self) -> Option<Url> {
-    self.shared_inbox_url.clone().map(|s| s.into_inner())
+  fn read_from_name_and_domain(
+    conn: &mut PgConnection,
+    community_name: &str,
+    protocol_domain: &str,
+  ) -> Result<Community, Error> {
+    use crate::schema::community::dsl::*;
+    community
+      .filter(lower(name).eq(lower(community_name)))
+      .filter(actor_id.like(format!("{}%", protocol_domain)))
+      .first::<Self>(conn)
   }
 }
 
 #[cfg(test)]
 mod tests {
   use crate::{
-    establish_unpooled_connection,
     source::{community::*, person::*},
     traits::{Bannable, Crud, Followable, Joinable},
+    utils::establish_unpooled_connection,
   };
   use serial_test::serial;
 
   #[test]
   #[serial]
   fn test_crud() {
-    let conn = establish_unpooled_connection();
+    let conn = &mut establish_unpooled_connection();
 
     let new_person = PersonForm {
       name: "bobbee".into(),
+      public_key: Some("pubkey".to_string()),
       ..PersonForm::default()
     };
 
-    let inserted_person = Person::create(&conn, &new_person).unwrap();
+    let inserted_person = Person::create(conn, &new_person).unwrap();
 
     let new_community = CommunityForm {
       name: "TIL".into(),
       title: "nada".to_owned(),
+      public_key: Some("pubkey".to_string()),
       ..CommunityForm::default()
     };
 
-    let inserted_community = Community::create(&conn, &new_community).unwrap();
+    let inserted_community = Community::create(conn, &new_community).unwrap();
 
     let expected_community = Community {
       id: inserted_community.id,
@@ -392,13 +425,15 @@ mod tests {
       actor_id: inserted_community.actor_id.to_owned(),
       local: true,
       private_key: None,
-      public_key: None,
+      public_key: "pubkey".to_owned(),
       last_refreshed_at: inserted_community.published,
       icon: None,
       banner: None,
       followers_url: inserted_community.followers_url.to_owned(),
       inbox_url: inserted_community.inbox_url.to_owned(),
       shared_inbox_url: None,
+      hidden: false,
+      posting_restricted_to_mods: false,
     };
 
     let community_follower_form = CommunityFollowerForm {
@@ -408,7 +443,7 @@ mod tests {
     };
 
     let inserted_community_follower =
-      CommunityFollower::follow(&conn, &community_follower_form).unwrap();
+      CommunityFollower::follow(conn, &community_follower_form).unwrap();
 
     let expected_community_follower = CommunityFollower {
       id: inserted_community_follower.id,
@@ -424,7 +459,7 @@ mod tests {
     };
 
     let inserted_community_moderator =
-      CommunityModerator::join(&conn, &community_moderator_form).unwrap();
+      CommunityModerator::join(conn, &community_moderator_form).unwrap();
 
     let expected_community_moderator = CommunityModerator {
       id: inserted_community_moderator.id,
@@ -436,26 +471,27 @@ mod tests {
     let community_person_ban_form = CommunityPersonBanForm {
       community_id: inserted_community.id,
       person_id: inserted_person.id,
+      expires: None,
     };
 
     let inserted_community_person_ban =
-      CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
+      CommunityPersonBan::ban(conn, &community_person_ban_form).unwrap();
 
     let expected_community_person_ban = CommunityPersonBan {
       id: inserted_community_person_ban.id,
       community_id: inserted_community.id,
       person_id: inserted_person.id,
       published: inserted_community_person_ban.published,
+      expires: None,
     };
 
-    let read_community = Community::read(&conn, inserted_community.id).unwrap();
-    let updated_community =
-      Community::update(&conn, inserted_community.id, &new_community).unwrap();
-    let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
-    let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
-    let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
-    let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
-    Person::delete(&conn, inserted_person.id).unwrap();
+    let read_community = Community::read(conn, inserted_community.id).unwrap();
+    let updated_community = Community::update(conn, inserted_community.id, &new_community).unwrap();
+    let ignored_community = CommunityFollower::unfollow(conn, &community_follower_form).unwrap();
+    let left_community = CommunityModerator::leave(conn, &community_moderator_form).unwrap();
+    let unban = CommunityPersonBan::unban(conn, &community_person_ban_form).unwrap();
+    let num_deleted = Community::delete(conn, inserted_community.id).unwrap();
+    Person::delete(conn, inserted_person.id).unwrap();
 
     assert_eq!(expected_community, read_community);
     assert_eq!(expected_community, inserted_community);