Add Custom Emojis Support (#2616)

* Add Custom Emojis

* Modify index
pull/2791/head
Anon 2023-03-20 16:32:31 -05:00 committed by GitHub
parent 66b0ddbbc5
commit 6bc49bdd70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 526 additions and 10 deletions

View File

@ -15,7 +15,7 @@ use lemmy_db_schema::{
}, },
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::{CustomEmojiView, SiteView};
use lemmy_db_views_actor::structs::PersonView; use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{error::LemmyError, version, ConnectionId}; 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 all_languages = Language::read_all(context.pool()).await?;
let discussion_languages = SiteLanguage::read_local(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 = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
let taglines = taglines_res.is_empty().then_some(taglines_res); let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view, site_view,
@ -77,6 +77,7 @@ impl Perform for LeaveAdmin {
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines, taglines,
custom_emojis,
}) })
} }
} }

View File

@ -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,
}

View File

@ -2,6 +2,7 @@ pub mod comment;
pub mod community; pub mod community;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod context; pub mod context;
pub mod custom_emoji;
pub mod person; pub mod person;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;

View File

@ -14,6 +14,7 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::{ use lemmy_db_views::structs::{
CommentView, CommentView,
CustomEmojiView,
LocalUserView, LocalUserView,
PostView, PostView,
RegistrationApplicationView, RegistrationApplicationView,
@ -224,7 +225,8 @@ pub struct GetSiteResponse {
pub federated_instances: Option<FederatedInstances>, // Federation may be disabled pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
pub all_languages: Vec<Language>, pub all_languages: Vec<Language>,
pub discussion_languages: Vec<LanguageId>, pub discussion_languages: Vec<LanguageId>,
pub taglines: Option<Vec<Tagline>>, pub taglines: Vec<Tagline>,
pub custom_emojis: Vec<CustomEmojiView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -118,6 +118,10 @@ pub enum UserOperationCrud {
GetPrivateMessages, GetPrivateMessages,
EditPrivateMessage, EditPrivateMessage,
DeletePrivateMessage, DeletePrivateMessage,
//Emojis
CreateCustomEmoji,
EditCustomEmoji,
DeleteCustomEmoji,
} }
#[derive(EnumString, Display, Debug, Clone)] #[derive(EnumString, Display, Debug, Clone)]

View File

@ -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 })
}
}

View File

@ -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,
})
}
}

View File

@ -0,0 +1,3 @@
mod create;
mod delete;
mod update;

View File

@ -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 })
}
}

View File

@ -4,6 +4,7 @@ use lemmy_utils::{error::LemmyError, ConnectionId};
mod comment; mod comment;
mod community; mod community;
mod custom_emoji;
mod post; mod post;
mod private_message; mod private_message;
mod site; mod site;

View File

@ -10,7 +10,7 @@ use lemmy_db_schema::source::{
language::Language, language::Language,
tagline::Tagline, tagline::Tagline,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::{CustomEmojiView, SiteView};
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
CommunityBlockView, CommunityBlockView,
CommunityFollowerView, CommunityFollowerView,
@ -88,8 +88,8 @@ impl PerformCrud for GetSite {
let all_languages = Language::read_all(context.pool()).await?; let all_languages = Language::read_all(context.pool()).await?;
let discussion_languages = SiteLanguage::read_local(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 = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
let taglines = (!taglines_res.is_empty()).then_some(taglines_res); let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view, site_view,
@ -101,6 +101,7 @@ impl PerformCrud for GetSite {
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines, taglines,
custom_emojis,
}) })
} }
} }

View File

@ -21,6 +21,13 @@ use lemmy_api_common::{
ListCommunitiesResponse, ListCommunitiesResponse,
TransferCommunity, TransferCommunity,
}, },
custom_emoji::{
CreateCustomEmoji,
CustomEmojiResponse,
DeleteCustomEmoji,
DeleteCustomEmojiResponse,
EditCustomEmoji,
},
person::{ person::{
AddAdmin, AddAdmin,
AddAdminResponse, AddAdminResponse,
@ -354,3 +361,15 @@ impl SendActivity for ListCommentReports {
impl SendActivity for ResolveCommentReport { impl SendActivity for ResolveCommentReport {
type Response = CommentReportResponse; type Response = CommentReportResponse;
} }
impl SendActivity for CreateCustomEmoji {
type Response = CustomEmojiResponse;
}
impl SendActivity for EditCustomEmoji {
type Response = CustomEmojiResponse;
}
impl SendActivity for DeleteCustomEmoji {
type Response = DeleteCustomEmojiResponse;
}

View File

@ -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
}
}

View File

@ -5,6 +5,7 @@ pub mod comment_reply;
pub mod comment_report; pub mod comment_report;
pub mod community; pub mod community;
pub mod community_block; pub mod community_block;
pub mod custom_emoji;
pub mod email_verification; pub mod email_verification;
pub mod federation_allowlist; pub mod federation_allowlist;
pub mod federation_blocklist; pub mod federation_blocklist;

View File

@ -108,6 +108,10 @@ pub struct InstanceId(i32);
#[cfg_attr(feature = "full", derive(DieselNewType))] #[cfg_attr(feature = "full", derive(DieselNewType))]
pub struct LocalSiteId(i32); 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)] #[repr(transparent)]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))] #[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]

View 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!(person_block -> person (person_id));
joinable!(comment -> person (creator_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 -> site (site_id));
joinable!(local_site_rate_limit -> local_site (local_site_id)); joinable!(local_site_rate_limit -> local_site (local_site_id));
joinable!(tagline -> 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!( allow_tables_to_appear_in_same_query!(
activity, activity,
@ -896,5 +919,7 @@ allow_tables_to_appear_in_same_query!(
federation_blocklist, federation_blocklist,
local_site, local_site,
local_site_rate_limit, local_site_rate_limit,
person_follower person_follower,
custom_emoji,
custom_emoji_keyword,
); );

View File

@ -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,
}

View File

@ -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,
}

View File

@ -6,6 +6,8 @@ pub mod comment_reply;
pub mod comment_report; pub mod comment_report;
pub mod community; pub mod community;
pub mod community_block; pub mod community_block;
pub mod custom_emoji;
pub mod custom_emoji_keyword;
pub mod email_verification; pub mod email_verification;
pub mod federation_allowlist; pub mod federation_allowlist;
pub mod federation_blocklist; pub mod federation_blocklist;

View File

@ -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
}
}

View File

@ -6,6 +6,8 @@ pub mod comment_report_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod comment_view; pub mod comment_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod custom_emoji_view;
#[cfg(feature = "full")]
pub mod local_user_view; pub mod local_user_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod post_report_view; pub mod post_report_view;

View File

@ -4,6 +4,8 @@ use lemmy_db_schema::{
comment::Comment, comment::Comment,
comment_report::CommentReport, comment_report::CommentReport,
community::Community, community::Community,
custom_emoji::CustomEmoji,
custom_emoji_keyword::CustomEmojiKeyword,
local_site::LocalSite, local_site::LocalSite,
local_site_rate_limit::LocalSiteRateLimit, local_site_rate_limit::LocalSiteRateLimit,
local_user::LocalUser, local_user::LocalUser,
@ -113,3 +115,8 @@ pub struct SiteView {
pub local_site_rate_limit: LocalSiteRateLimit, pub local_site_rate_limit: LocalSiteRateLimit,
pub counts: SiteAggregates, pub counts: SiteAggregates,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CustomEmojiView {
pub custom_emoji: CustomEmoji,
pub keywords: Vec<CustomEmojiKeyword>,
}

View File

@ -70,7 +70,7 @@ services:
restart: always restart: always
pictrs: 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 # this needs to match the pictrs url in lemmy.hjson
hostname: pictrs hostname: pictrs
# we can set options to pictrs like this, here we set max. image size and forced format for conversion # 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 - PICTRS__API_KEY=API_KEY
- RUST_LOG=debug - RUST_LOG=debug
- RUST_BACKTRACE=full - 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 user: 991:991
volumes: volumes:
- ./volumes/pictrs:/mnt - ./volumes/pictrs:/mnt

View File

@ -22,7 +22,7 @@ services:
pictrs: pictrs:
restart: always restart: always
image: asonix/pictrs:0.3.1 image: asonix/pictrs:0.4.0-beta.19
user: 991:991 user: 991:991
volumes: volumes:
- ./volumes/pictrs_alpha:/mnt - ./volumes/pictrs_alpha:/mnt

View File

@ -0,0 +1,2 @@
drop table custom_emoji_keyword;
drop table custom_emoji;

View File

@ -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);

View File

@ -31,6 +31,7 @@ use lemmy_api_common::{
TransferCommunity, TransferCommunity,
}, },
context::LemmyContext, context::LemmyContext,
custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
person::{ person::{
AddAdmin, AddAdmin,
BanPerson, BanPerson,
@ -352,6 +353,16 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.route("/community", web::post().to(route_post::<PurgeCommunity>)) .route("/community", web::post().to(route_post::<PurgeCommunity>))
.route("/post", web::post().to(route_post::<PurgePost>)) .route("/post", web::post().to(route_post::<PurgePost>))
.route("/comment", web::post().to(route_post::<PurgeComment>)), .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>),
),
), ),
); );
} }

View File

@ -32,6 +32,7 @@ use lemmy_api_common::{
TransferCommunity, TransferCommunity,
}, },
context::LemmyContext, context::LemmyContext,
custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
person::{ person::{
AddAdmin, AddAdmin,
BanPerson, BanPerson,
@ -389,6 +390,16 @@ pub async fn match_websocket_operation_crud(
UserOperationCrud::GetComment => { UserOperationCrud::GetComment => {
do_websocket_operation_crud::<GetComment>(context, id, op, data).await 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
}
} }
} }