]> Untitled Git - lemmy.git/commitdiff
Add Custom Emojis Support (#2616)
authorAnon <makotech222@users.noreply.github.com>
Mon, 20 Mar 2023 21:32:31 +0000 (16:32 -0500)
committerGitHub <noreply@github.com>
Mon, 20 Mar 2023 21:32:31 +0000 (17:32 -0400)
* Add Custom Emojis

* Modify index

28 files changed:
crates/api/src/site/leave_admin.rs
crates/api_common/src/custom_emoji.rs [new file with mode: 0644]
crates/api_common/src/lib.rs
crates/api_common/src/site.rs
crates/api_common/src/websocket/mod.rs
crates/api_crud/src/custom_emoji/create.rs [new file with mode: 0644]
crates/api_crud/src/custom_emoji/delete.rs [new file with mode: 0644]
crates/api_crud/src/custom_emoji/mod.rs [new file with mode: 0644]
crates/api_crud/src/custom_emoji/update.rs [new file with mode: 0644]
crates/api_crud/src/lib.rs
crates/api_crud/src/site/read.rs
crates/apub/src/activities/unfederated.rs
crates/db_schema/src/impls/custom_emoji.rs [new file with mode: 0644]
crates/db_schema/src/impls/mod.rs
crates/db_schema/src/newtypes.rs
crates/db_schema/src/schema.rs
crates/db_schema/src/source/custom_emoji.rs [new file with mode: 0644]
crates/db_schema/src/source/custom_emoji_keyword.rs [new file with mode: 0644]
crates/db_schema/src/source/mod.rs
crates/db_views/src/custom_emoji_view.rs [new file with mode: 0644]
crates/db_views/src/lib.rs
crates/db_views/src/structs.rs
docker/docker-compose.yml
docker/federation/docker-compose.yml
migrations/2023-02-11-173347_custom_emojis/down.sql [new file with mode: 0644]
migrations/2023-02-11-173347_custom_emojis/up.sql [new file with mode: 0644]
src/api_routes_http.rs
src/api_routes_websocket.rs

index ebe697907df6445f73b16c4588a73782aa4e8271..84515b9fec21a3ae4b3d7f2042081e04e4313ec6 100644 (file)
@@ -15,7 +15,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::SiteView;
+use lemmy_db_views::structs::{CustomEmojiView, SiteView};
 use lemmy_db_views_actor::structs::PersonView;
 use lemmy_utils::{error::LemmyError, version, ConnectionId};
 
@@ -64,8 +64,8 @@ impl Perform for LeaveAdmin {
 
     let all_languages = Language::read_all(context.pool()).await?;
     let discussion_languages = SiteLanguage::read_local(context.pool()).await?;
-    let taglines_res = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
-    let taglines = taglines_res.is_empty().then_some(taglines_res);
+    let taglines = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
+    let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
 
     Ok(GetSiteResponse {
       site_view,
@@ -77,6 +77,7 @@ impl Perform for LeaveAdmin {
       all_languages,
       discussion_languages,
       taglines,
+      custom_emojis,
     })
   }
 }
diff --git a/crates/api_common/src/custom_emoji.rs b/crates/api_common/src/custom_emoji.rs
new file mode 100644 (file)
index 0000000..4652529
--- /dev/null
@@ -0,0 +1,42 @@
+use crate::sensitive::Sensitive;
+use lemmy_db_schema::newtypes::CustomEmojiId;
+use lemmy_db_views::structs::CustomEmojiView;
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CreateCustomEmoji {
+  pub category: String,
+  pub shortcode: String,
+  pub image_url: Url,
+  pub alt_text: String,
+  pub keywords: Vec<String>,
+  pub auth: Sensitive<String>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct EditCustomEmoji {
+  pub id: CustomEmojiId,
+  pub category: String,
+  pub image_url: Url,
+  pub alt_text: String,
+  pub keywords: Vec<String>,
+  pub auth: Sensitive<String>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+pub struct DeleteCustomEmoji {
+  pub id: CustomEmojiId,
+  pub auth: Sensitive<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeleteCustomEmojiResponse {
+  pub id: CustomEmojiId,
+  pub success: bool,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CustomEmojiResponse {
+  pub custom_emoji: CustomEmojiView,
+}
index 29686aba6d6bfb521d912166c274f0038e271e34..3c9d71216f71a0f83e0b2948cc1853e4df5fcbe9 100644 (file)
@@ -2,6 +2,7 @@ pub mod comment;
 pub mod community;
 #[cfg(feature = "full")]
 pub mod context;
+pub mod custom_emoji;
 pub mod person;
 pub mod post;
 pub mod private_message;
index 0000ecd3d7c51aef2dd8618f3f80b7a0ef42ca56..920cc6ec62d248b7e4af76734b568a58433e674a 100644 (file)
@@ -14,6 +14,7 @@ use lemmy_db_schema::{
 };
 use lemmy_db_views::structs::{
   CommentView,
+  CustomEmojiView,
   LocalUserView,
   PostView,
   RegistrationApplicationView,
@@ -224,7 +225,8 @@ pub struct GetSiteResponse {
   pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
   pub all_languages: Vec<Language>,
   pub discussion_languages: Vec<LanguageId>,
-  pub taglines: Option<Vec<Tagline>>,
+  pub taglines: Vec<Tagline>,
+  pub custom_emojis: Vec<CustomEmojiView>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
index 76f17ae9ff7af8c4b2bdab1b099bc7fbe524da30..21c5338541aba35e76c0aa4d338924985285bddc 100644 (file)
@@ -118,6 +118,10 @@ pub enum UserOperationCrud {
   GetPrivateMessages,
   EditPrivateMessage,
   DeletePrivateMessage,
+  //Emojis
+  CreateCustomEmoji,
+  EditCustomEmoji,
+  DeleteCustomEmoji,
 }
 
 #[derive(EnumString, Display, Debug, Clone)]
diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs
new file mode 100644 (file)
index 0000000..42b6c5d
--- /dev/null
@@ -0,0 +1,54 @@
+use crate::PerformCrud;
+use actix_web::web::Data;
+use lemmy_api_common::{
+  context::LemmyContext,
+  custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
+  utils::{get_local_user_view_from_jwt, is_admin},
+};
+use lemmy_db_schema::source::{
+  custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
+  custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
+  local_site::LocalSite,
+};
+use lemmy_db_views::structs::CustomEmojiView;
+use lemmy_utils::{error::LemmyError, ConnectionId};
+
+#[async_trait::async_trait(?Send)]
+impl PerformCrud for CreateCustomEmoji {
+  type Response = CustomEmojiResponse;
+
+  #[tracing::instrument(skip(self, context, _websocket_id))]
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    _websocket_id: Option<ConnectionId>,
+  ) -> Result<CustomEmojiResponse, LemmyError> {
+    let data: &CreateCustomEmoji = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+    let local_site = LocalSite::read(context.pool()).await?;
+    // Make sure user is an admin
+    is_admin(&local_user_view)?;
+
+    let emoji_form = CustomEmojiInsertForm::builder()
+      .local_site_id(local_site.id)
+      .shortcode(data.shortcode.to_lowercase().trim().to_string())
+      .alt_text(data.alt_text.to_string())
+      .category(data.category.to_string())
+      .image_url(data.clone().image_url.into())
+      .build();
+    let emoji = CustomEmoji::create(context.pool(), &emoji_form).await?;
+    let mut keywords = vec![];
+    for keyword in &data.keywords {
+      let keyword_form = CustomEmojiKeywordInsertForm::builder()
+        .custom_emoji_id(emoji.id)
+        .keyword(keyword.to_lowercase().trim().to_string())
+        .build();
+      keywords.push(keyword_form);
+    }
+    CustomEmojiKeyword::create(context.pool(), keywords).await?;
+    let view = CustomEmojiView::get(context.pool(), emoji.id).await?;
+    Ok(CustomEmojiResponse { custom_emoji: view })
+  }
+}
diff --git a/crates/api_crud/src/custom_emoji/delete.rs b/crates/api_crud/src/custom_emoji/delete.rs
new file mode 100644 (file)
index 0000000..db8c973
--- /dev/null
@@ -0,0 +1,33 @@
+use crate::PerformCrud;
+use actix_web::web::Data;
+use lemmy_api_common::{
+  context::LemmyContext,
+  custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
+  utils::{get_local_user_view_from_jwt, is_admin},
+};
+use lemmy_db_schema::source::custom_emoji::CustomEmoji;
+use lemmy_utils::{error::LemmyError, ConnectionId};
+
+#[async_trait::async_trait(?Send)]
+impl PerformCrud for DeleteCustomEmoji {
+  type Response = DeleteCustomEmojiResponse;
+
+  #[tracing::instrument(skip(self, context, _websocket_id))]
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    _websocket_id: Option<ConnectionId>,
+  ) -> Result<DeleteCustomEmojiResponse, LemmyError> {
+    let data: &DeleteCustomEmoji = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+    // Make sure user is an admin
+    is_admin(&local_user_view)?;
+    CustomEmoji::delete(context.pool(), data.id).await?;
+    Ok(DeleteCustomEmojiResponse {
+      id: data.id,
+      success: true,
+    })
+  }
+}
diff --git a/crates/api_crud/src/custom_emoji/mod.rs b/crates/api_crud/src/custom_emoji/mod.rs
new file mode 100644 (file)
index 0000000..b9d8b55
--- /dev/null
@@ -0,0 +1,3 @@
+mod create;
+mod delete;
+mod update;
diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs
new file mode 100644 (file)
index 0000000..6233cbe
--- /dev/null
@@ -0,0 +1,54 @@
+use crate::PerformCrud;
+use actix_web::web::Data;
+use lemmy_api_common::{
+  context::LemmyContext,
+  custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
+  utils::{get_local_user_view_from_jwt, is_admin},
+};
+use lemmy_db_schema::source::{
+  custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
+  custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
+  local_site::LocalSite,
+};
+use lemmy_db_views::structs::CustomEmojiView;
+use lemmy_utils::{error::LemmyError, ConnectionId};
+
+#[async_trait::async_trait(?Send)]
+impl PerformCrud for EditCustomEmoji {
+  type Response = CustomEmojiResponse;
+
+  #[tracing::instrument(skip(self, context, _websocket_id))]
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    _websocket_id: Option<ConnectionId>,
+  ) -> Result<CustomEmojiResponse, LemmyError> {
+    let data: &EditCustomEmoji = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+    let local_site = LocalSite::read(context.pool()).await?;
+    // Make sure user is an admin
+    is_admin(&local_user_view)?;
+
+    let emoji_form = CustomEmojiUpdateForm::builder()
+      .local_site_id(local_site.id)
+      .alt_text(data.alt_text.to_string())
+      .category(data.category.to_string())
+      .image_url(data.clone().image_url.into())
+      .build();
+    let emoji = CustomEmoji::update(context.pool(), data.id, &emoji_form).await?;
+    CustomEmojiKeyword::delete(context.pool(), data.id).await?;
+    let mut keywords = vec![];
+    for keyword in &data.keywords {
+      let keyword_form = CustomEmojiKeywordInsertForm::builder()
+        .custom_emoji_id(emoji.id)
+        .keyword(keyword.to_lowercase().trim().to_string())
+        .build();
+      keywords.push(keyword_form);
+    }
+    CustomEmojiKeyword::create(context.pool(), keywords).await?;
+    let view = CustomEmojiView::get(context.pool(), emoji.id).await?;
+    Ok(CustomEmojiResponse { custom_emoji: view })
+  }
+}
index d37dfbee243133ec56a8650f5d6487efb62f8e12..70d5d44d4e4487ef8510d148fcf3c9c84b748d6c 100644 (file)
@@ -4,6 +4,7 @@ use lemmy_utils::{error::LemmyError, ConnectionId};
 
 mod comment;
 mod community;
+mod custom_emoji;
 mod post;
 mod private_message;
 mod site;
index 51457b8834d0448698bc049d078396bba78b8e7d..c89dbef702522a1fe1bf43a13a059e7e154059d0 100644 (file)
@@ -10,7 +10,7 @@ use lemmy_db_schema::source::{
   language::Language,
   tagline::Tagline,
 };
-use lemmy_db_views::structs::SiteView;
+use lemmy_db_views::structs::{CustomEmojiView, SiteView};
 use lemmy_db_views_actor::structs::{
   CommunityBlockView,
   CommunityFollowerView,
@@ -88,8 +88,8 @@ impl PerformCrud for GetSite {
 
     let all_languages = Language::read_all(context.pool()).await?;
     let discussion_languages = SiteLanguage::read_local(context.pool()).await?;
-    let taglines_res = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
-    let taglines = (!taglines_res.is_empty()).then_some(taglines_res);
+    let taglines = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
+    let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
 
     Ok(GetSiteResponse {
       site_view,
@@ -101,6 +101,7 @@ impl PerformCrud for GetSite {
       all_languages,
       discussion_languages,
       taglines,
+      custom_emojis,
     })
   }
 }
index 6cb4d44d90399c09917669333026c9fae1fd96bc..e2fff292de8b913eab0ee9cd5b6bde0d7d7c5e7c 100644 (file)
@@ -21,6 +21,13 @@ use lemmy_api_common::{
     ListCommunitiesResponse,
     TransferCommunity,
   },
+  custom_emoji::{
+    CreateCustomEmoji,
+    CustomEmojiResponse,
+    DeleteCustomEmoji,
+    DeleteCustomEmojiResponse,
+    EditCustomEmoji,
+  },
   person::{
     AddAdmin,
     AddAdminResponse,
@@ -354,3 +361,15 @@ impl SendActivity for ListCommentReports {
 impl SendActivity for ResolveCommentReport {
   type Response = CommentReportResponse;
 }
+
+impl SendActivity for CreateCustomEmoji {
+  type Response = CustomEmojiResponse;
+}
+
+impl SendActivity for EditCustomEmoji {
+  type Response = CustomEmojiResponse;
+}
+
+impl SendActivity for DeleteCustomEmoji {
+  type Response = DeleteCustomEmojiResponse;
+}
diff --git a/crates/db_schema/src/impls/custom_emoji.rs b/crates/db_schema/src/impls/custom_emoji.rs
new file mode 100644 (file)
index 0000000..cce35df
--- /dev/null
@@ -0,0 +1,60 @@
+use crate::{
+  newtypes::CustomEmojiId,
+  schema::{
+    custom_emoji::dsl::custom_emoji,
+    custom_emoji_keyword::dsl::{custom_emoji_id, custom_emoji_keyword},
+  },
+  source::{
+    custom_emoji::{CustomEmoji, CustomEmojiInsertForm, CustomEmojiUpdateForm},
+    custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
+  },
+  utils::{get_conn, DbPool},
+};
+use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
+use diesel_async::RunQueryDsl;
+
+impl CustomEmoji {
+  pub async fn create(pool: &DbPool, form: &CustomEmojiInsertForm) -> Result<Self, Error> {
+    let conn = &mut get_conn(pool).await?;
+    insert_into(custom_emoji)
+      .values(form)
+      .get_result::<Self>(conn)
+      .await
+  }
+  pub async fn update(
+    pool: &DbPool,
+    emoji_id: CustomEmojiId,
+    form: &CustomEmojiUpdateForm,
+  ) -> Result<Self, Error> {
+    let conn = &mut get_conn(pool).await?;
+    diesel::update(custom_emoji.find(emoji_id))
+      .set(form)
+      .get_result::<Self>(conn)
+      .await
+  }
+  pub async fn delete(pool: &DbPool, emoji_id: CustomEmojiId) -> Result<usize, Error> {
+    let conn = &mut get_conn(pool).await?;
+    diesel::delete(custom_emoji.find(emoji_id))
+      .execute(conn)
+      .await
+  }
+}
+
+impl CustomEmojiKeyword {
+  pub async fn create(
+    pool: &DbPool,
+    form: Vec<CustomEmojiKeywordInsertForm>,
+  ) -> Result<Vec<Self>, Error> {
+    let conn = &mut get_conn(pool).await?;
+    insert_into(custom_emoji_keyword)
+      .values(form)
+      .get_results::<Self>(conn)
+      .await
+  }
+  pub async fn delete(pool: &DbPool, emoji_id: CustomEmojiId) -> Result<usize, Error> {
+    let conn = &mut get_conn(pool).await?;
+    diesel::delete(custom_emoji_keyword.filter(custom_emoji_id.eq(emoji_id)))
+      .execute(conn)
+      .await
+  }
+}
index 456565adf00516ff8ff6145dee997864e7f35994..915d1c8e2c99c9774a20620b29460f344ba7c3da 100644 (file)
@@ -5,6 +5,7 @@ pub mod comment_reply;
 pub mod comment_report;
 pub mod community;
 pub mod community_block;
+pub mod custom_emoji;
 pub mod email_verification;
 pub mod federation_allowlist;
 pub mod federation_blocklist;
index 7e2465bb70b0eb18fd9ef0d927894fcd4dccb766..da2c9b9e5ccdcdf670d5d24ebf6db70be44a999e 100644 (file)
@@ -108,6 +108,10 @@ pub struct InstanceId(i32);
 #[cfg_attr(feature = "full", derive(DieselNewType))]
 pub struct LocalSiteId(i32);
 
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
+#[cfg_attr(feature = "full", derive(DieselNewType))]
+pub struct CustomEmojiId(i32);
+
 #[repr(transparent)]
 #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
 #[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
index 870754a299a71afa5fe59ffd72ffbd749687dbb0..b4fbefe2e004bf5767acd447d5100af07b28e5ab 100644 (file)
@@ -751,6 +751,27 @@ table! {
     }
 }
 
+table! {
+  custom_emoji (id) {
+    id -> Int4,
+    local_site_id -> Int4,
+    shortcode -> Varchar,
+    image_url -> Text,
+    alt_text -> Text,
+    category -> Text,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    }
+}
+
+table! {
+  custom_emoji_keyword (id) {
+    id -> Int4,
+    custom_emoji_id -> Int4,
+    keyword -> Varchar,
+    }
+}
+
 joinable!(person_block -> person (person_id));
 
 joinable!(comment -> person (creator_id));
@@ -836,6 +857,8 @@ joinable!(federation_blocklist -> instance (instance_id));
 joinable!(local_site -> site (site_id));
 joinable!(local_site_rate_limit -> local_site (local_site_id));
 joinable!(tagline -> local_site (local_site_id));
+joinable!(custom_emoji -> local_site (local_site_id));
+joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
 
 allow_tables_to_appear_in_same_query!(
   activity,
@@ -896,5 +919,7 @@ allow_tables_to_appear_in_same_query!(
   federation_blocklist,
   local_site,
   local_site_rate_limit,
-  person_follower
+  person_follower,
+  custom_emoji,
+  custom_emoji_keyword,
 );
diff --git a/crates/db_schema/src/source/custom_emoji.rs b/crates/db_schema/src/source/custom_emoji.rs
new file mode 100644 (file)
index 0000000..8a09281
--- /dev/null
@@ -0,0 +1,44 @@
+use crate::newtypes::{CustomEmojiId, DbUrl, LocalSiteId};
+#[cfg(feature = "full")]
+use crate::schema::custom_emoji;
+use serde::{Deserialize, Serialize};
+use typed_builder::TypedBuilder;
+
+#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
+#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
+#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))]
+#[cfg_attr(
+  feature = "full",
+  diesel(belongs_to(crate::source::local_site::LocalSite))
+)]
+pub struct CustomEmoji {
+  pub id: CustomEmojiId,
+  pub local_site_id: LocalSiteId,
+  pub shortcode: String,
+  pub image_url: DbUrl,
+  pub alt_text: String,
+  pub category: String,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Debug, Clone, TypedBuilder)]
+#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
+#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))]
+pub struct CustomEmojiInsertForm {
+  pub local_site_id: LocalSiteId,
+  pub shortcode: String,
+  pub image_url: DbUrl,
+  pub alt_text: String,
+  pub category: String,
+}
+
+#[derive(Debug, Clone, TypedBuilder)]
+#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
+#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))]
+pub struct CustomEmojiUpdateForm {
+  pub local_site_id: LocalSiteId,
+  pub image_url: DbUrl,
+  pub alt_text: String,
+  pub category: String,
+}
diff --git a/crates/db_schema/src/source/custom_emoji_keyword.rs b/crates/db_schema/src/source/custom_emoji_keyword.rs
new file mode 100644 (file)
index 0000000..caebcd8
--- /dev/null
@@ -0,0 +1,26 @@
+use crate::newtypes::CustomEmojiId;
+#[cfg(feature = "full")]
+use crate::schema::custom_emoji_keyword;
+use serde::{Deserialize, Serialize};
+use typed_builder::TypedBuilder;
+
+#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
+#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
+#[cfg_attr(feature = "full", diesel(table_name = custom_emoji_keyword))]
+#[cfg_attr(
+  feature = "full",
+  diesel(belongs_to(crate::source::custom_emoji::CustomEmoji))
+)]
+pub struct CustomEmojiKeyword {
+  pub id: i32,
+  pub custom_emoji_id: CustomEmojiId,
+  pub keyword: String,
+}
+
+#[derive(Debug, Clone, TypedBuilder)]
+#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
+#[cfg_attr(feature = "full", diesel(table_name = custom_emoji_keyword))]
+pub struct CustomEmojiKeywordInsertForm {
+  pub custom_emoji_id: CustomEmojiId,
+  pub keyword: String,
+}
index dbc83211fe00a3824e0d29e984410456dea0c772..9aab4b90b35753023f5142c22b74e2687521c5bf 100644 (file)
@@ -6,6 +6,8 @@ pub mod comment_reply;
 pub mod comment_report;
 pub mod community;
 pub mod community_block;
+pub mod custom_emoji;
+pub mod custom_emoji_keyword;
 pub mod email_verification;
 pub mod federation_allowlist;
 pub mod federation_blocklist;
diff --git a/crates/db_views/src/custom_emoji_view.rs b/crates/db_views/src/custom_emoji_view.rs
new file mode 100644 (file)
index 0000000..66d583e
--- /dev/null
@@ -0,0 +1,82 @@
+use crate::structs::CustomEmojiView;
+use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl};
+use diesel_async::RunQueryDsl;
+use lemmy_db_schema::{
+  newtypes::{CustomEmojiId, LocalSiteId},
+  schema::{custom_emoji, custom_emoji_keyword},
+  source::{custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword},
+  utils::{get_conn, DbPool},
+};
+use std::collections::HashMap;
+
+type CustomEmojiTuple = (CustomEmoji, Option<CustomEmojiKeyword>);
+
+impl CustomEmojiView {
+  pub async fn get(pool: &DbPool, emoji_id: CustomEmojiId) -> Result<Self, Error> {
+    let conn = &mut get_conn(pool).await?;
+    let emojis = custom_emoji::table
+      .find(emoji_id)
+      .left_join(
+        custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)),
+      )
+      .select((
+        custom_emoji::all_columns,
+        custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want)
+      ))
+      .load::<CustomEmojiTuple>(conn)
+      .await?;
+    if let Some(emoji) = CustomEmojiView::from_tuple_to_vec(emojis)
+      .into_iter()
+      .next()
+    {
+      Ok(emoji)
+    } else {
+      Err(diesel::result::Error::NotFound)
+    }
+  }
+
+  pub async fn get_all(pool: &DbPool, for_local_site_id: LocalSiteId) -> Result<Vec<Self>, Error> {
+    let conn = &mut get_conn(pool).await?;
+    let emojis = custom_emoji::table
+      .filter(custom_emoji::local_site_id.eq(for_local_site_id))
+      .left_join(
+        custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)),
+      )
+      .order(custom_emoji::category)
+      .then_order_by(custom_emoji::id)
+      .select((
+        custom_emoji::all_columns,
+        custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want)
+      ))
+      .load::<CustomEmojiTuple>(conn)
+      .await?;
+
+    Ok(CustomEmojiView::from_tuple_to_vec(emojis))
+  }
+
+  fn from_tuple_to_vec(items: Vec<CustomEmojiTuple>) -> Vec<Self> {
+    let mut result = Vec::new();
+    let mut hash: HashMap<CustomEmojiId, Vec<CustomEmojiKeyword>> = HashMap::new();
+    for item in &items {
+      let emoji_id: CustomEmojiId = item.0.id;
+      if let std::collections::hash_map::Entry::Vacant(e) = hash.entry(emoji_id) {
+        e.insert(Vec::new());
+        result.push(CustomEmojiView {
+          custom_emoji: item.0.clone(),
+          keywords: Vec::new(),
+        })
+      }
+      if let Some(item_keyword) = &item.1 {
+        if let Some(keywords) = hash.get_mut(&emoji_id) {
+          keywords.push(item_keyword.clone())
+        }
+      }
+    }
+    for emoji in &mut result {
+      if let Some(keywords) = hash.get_mut(&emoji.custom_emoji.id) {
+        emoji.keywords = keywords.clone();
+      }
+    }
+    result
+  }
+}
index 9f15002376ee23c35b20420b8de5c51b49933783..8abf776ba50984a0c8c578e6982c86cd2ace8b23 100644 (file)
@@ -6,6 +6,8 @@ pub mod comment_report_view;
 #[cfg(feature = "full")]
 pub mod comment_view;
 #[cfg(feature = "full")]
+pub mod custom_emoji_view;
+#[cfg(feature = "full")]
 pub mod local_user_view;
 #[cfg(feature = "full")]
 pub mod post_report_view;
index 4521221f295c18b1c12496fd28263a6ac2d1ee77..b3b2b1c7a51b942c3fbddfff982afe8c6f24406f 100644 (file)
@@ -4,6 +4,8 @@ use lemmy_db_schema::{
     comment::Comment,
     comment_report::CommentReport,
     community::Community,
+    custom_emoji::CustomEmoji,
+    custom_emoji_keyword::CustomEmojiKeyword,
     local_site::LocalSite,
     local_site_rate_limit::LocalSiteRateLimit,
     local_user::LocalUser,
@@ -113,3 +115,8 @@ pub struct SiteView {
   pub local_site_rate_limit: LocalSiteRateLimit,
   pub counts: SiteAggregates,
 }
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CustomEmojiView {
+  pub custom_emoji: CustomEmoji,
+  pub keywords: Vec<CustomEmojiKeyword>,
+}
index 7ed15150c426c9159a6b37a0851377054b534126..b46c1f8e28dca3bd2602484522df9360f4bf8666 100644 (file)
@@ -70,7 +70,7 @@ services:
     restart: always
 
   pictrs:
-    image: asonix/pictrs:0.3.1
+    image: asonix/pictrs:0.4.0-beta.19
     # this needs to match the pictrs url in lemmy.hjson
     hostname: pictrs
     # we can set options to pictrs like this, here we set max. image size and forced format for conversion
@@ -82,6 +82,11 @@ services:
       - PICTRS__API_KEY=API_KEY
       - RUST_LOG=debug
       - RUST_BACKTRACE=full
+      - PICTRS__MEDIA__VIDEO_CODEC=vp9
+      - PICTRS__MEDIA__GIF__MAX_WIDTH=256
+      - PICTRS__MEDIA__GIF__MAX_HEIGHT=256
+      - PICTRS__MEDIA__GIF__MAX_AREA=65536
+      - PICTRS__MEDIA__GIF__MAX_FRAME_COUNT=400
     user: 991:991
     volumes:
       - ./volumes/pictrs:/mnt
index ab512f49f05bdb6c4561b9c9b1fe22a8d6ffe094..13576df1072a5558287bdf47ed6352647be7a587 100644 (file)
@@ -22,7 +22,7 @@ services:
 
   pictrs:
     restart: always
-    image: asonix/pictrs:0.3.1
+    image: asonix/pictrs:0.4.0-beta.19
     user: 991:991
     volumes:
       - ./volumes/pictrs_alpha:/mnt
diff --git a/migrations/2023-02-11-173347_custom_emojis/down.sql b/migrations/2023-02-11-173347_custom_emojis/down.sql
new file mode 100644 (file)
index 0000000..8994bb3
--- /dev/null
@@ -0,0 +1,2 @@
+drop table custom_emoji_keyword;
+drop table custom_emoji;
\ No newline at end of file
diff --git a/migrations/2023-02-11-173347_custom_emojis/up.sql b/migrations/2023-02-11-173347_custom_emojis/up.sql
new file mode 100644 (file)
index 0000000..79a21b2
--- /dev/null
@@ -0,0 +1,19 @@
+create table custom_emoji (
+  id serial primary key,
+  local_site_id int references local_site on update cascade on delete cascade not null,
+  shortcode varchar(128) not null UNIQUE,
+  image_url text not null UNIQUE,
+  alt_text text not null,
+  category text not null,
+  published timestamp without time zone default now() not null,
+  updated timestamp without time zone
+);
+
+create table custom_emoji_keyword (
+  id serial primary key,
+  custom_emoji_id int references custom_emoji on update cascade on delete cascade not null,
+  keyword varchar(128) not null,
+  UNIQUE (custom_emoji_id, keyword)
+);
+
+create index idx_custom_emoji_category on custom_emoji (id,category);
index 882efc8ce477e0d0952d75c5bef45f0e27c85393..8ab6980e53a02255ba0578a23c1a198e15185233 100644 (file)
@@ -31,6 +31,7 @@ use lemmy_api_common::{
     TransferCommunity,
   },
   context::LemmyContext,
+  custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
   person::{
     AddAdmin,
     BanPerson,
@@ -352,6 +353,16 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
           .route("/community", web::post().to(route_post::<PurgeCommunity>))
           .route("/post", web::post().to(route_post::<PurgePost>))
           .route("/comment", web::post().to(route_post::<PurgeComment>)),
+      )
+      .service(
+        web::scope("/custom_emoji")
+          .wrap(rate_limit.message())
+          .route("", web::post().to(route_post_crud::<CreateCustomEmoji>))
+          .route("", web::put().to(route_post_crud::<EditCustomEmoji>))
+          .route(
+            "/delete",
+            web::post().to(route_post_crud::<DeleteCustomEmoji>),
+          ),
       ),
   );
 }
index fdaba6ba545fd04953cb6c951cc130310866a0f5..b8d8f2c24bbec25bae0cca70fba1f0c0f422d24b 100644 (file)
@@ -32,6 +32,7 @@ use lemmy_api_common::{
     TransferCommunity,
   },
   context::LemmyContext,
+  custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
   person::{
     AddAdmin,
     BanPerson,
@@ -389,6 +390,16 @@ pub async fn match_websocket_operation_crud(
     UserOperationCrud::GetComment => {
       do_websocket_operation_crud::<GetComment>(context, id, op, data).await
     }
+    // Emojis
+    UserOperationCrud::CreateCustomEmoji => {
+      do_websocket_operation_crud::<CreateCustomEmoji>(context, id, op, data).await
+    }
+    UserOperationCrud::EditCustomEmoji => {
+      do_websocket_operation_crud::<EditCustomEmoji>(context, id, op, data).await
+    }
+    UserOperationCrud::DeleteCustomEmoji => {
+      do_websocket_operation_crud::<DeleteCustomEmoji>(context, id, op, data).await
+    }
   }
 }