mirror of https://github.com/LemmyNet/lemmy
parent
66b0ddbbc5
commit
6bc49bdd70
@ -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,
|
||||
}
|
@ -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 })
|
||||
}
|
||||
}
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
mod create;
|
||||
mod delete;
|
||||
mod update;
|
@ -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 })
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
@ -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,
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
drop table custom_emoji_keyword;
|
||||
drop table custom_emoji;
|
@ -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);
|
Loading…
Reference in New Issue