tetratto/crates/app/src/routes/api/v1/communities/emojis.rs

236 lines
6.2 KiB
Rust
Raw Normal View History

use std::fs::exists;
use image::ImageFormat;
use pathbufd::PathBufD;
use crate::{
get_user_from_token,
image::{save_buffer, Image},
routes::api::v1::{auth::images::read_image, UpdateEmojiName},
State,
};
use axum::{body::Body, extract::Path, response::IntoResponse, Extension, Json};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{
uploads::{CustomEmoji, MediaType, MediaUpload},
ApiReturn, Error,
};
2025-05-05 23:44:10 -04:00
/// Expand a unicode emoji into its Gemoji shortcode.
pub async fn get_emoji_shortcode(emoji: String) -> impl IntoResponse {
match emojis::get(&emoji) {
Some(e) => match e.shortcode() {
Some(s) => s.to_string(),
None => e.name().replace(" ", "-"),
},
None => String::new(),
}
}
pub async fn get_request(
Path((community, name)): Path<(usize, String)>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
if community == 0 {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
let emoji = match data.get_emoji_by_community_name(community, &name).await {
Ok(ua) => ua,
Err(_) => {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
};
let upload = data
.get_upload_by_id(emoji.0.unwrap().upload_id)
.await
.unwrap();
let path = upload.path(&data.0);
if !exists(&path).unwrap() {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
Ok((
[("Content-Type", upload.what.mime())],
Body::from(read_image(path)),
))
}
// maximum file dimensions: 512x512px (256KiB)
pub const MAXIUMUM_FILE_SIZE: usize = 262144;
pub async fn create_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path((community, name)): Path<(usize, String)>,
img: Image,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
// check file size
if img.0.len() > MAXIUMUM_FILE_SIZE {
return Json(Error::DataTooLong("image".to_string()).into());
}
// make sure emoji doesn't already exist in community
if data
.get_emoji_by_community_name(community, &name)
.await
.is_ok()
{
return Json(
Error::MiscError("This emoji name is already in use in this community".to_string())
.into(),
);
}
// create upload
let upload = match data
.create_upload(MediaUpload::new(
if img.1 == "image/gif" {
MediaType::Gif
} else {
MediaType::Webp
},
user.id,
))
.await
{
Ok(u) => u,
Err(e) => return Json(e.into()),
};
// create emoji
let is_animated = img.1 == "image/gif";
match data
.create_emoji(CustomEmoji::new(
user.id,
community,
upload.id,
name,
is_animated,
))
.await
{
Ok(_) => {
if is_animated {
if let Err(e) = upload.write(&data.0, &img.0) {
return Json(Error::MiscError(e.to_string()).into());
}
} else {
if let Err(e) = save_buffer(
&upload.path(&data.0).to_string(),
img.0.to_vec(),
ImageFormat::WebP,
) {
return Json(Error::MiscError(e.to_string()).into());
}
}
Json(ApiReturn {
ok: true,
message: "Emoji created".to_string(),
payload: (),
})
}
Err(e) => {
if let Err(e) = upload.remove(&data.0) {
return Json(e.into());
}
Json(e.into())
}
}
}
pub async fn update_name_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateEmojiName>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.update_emoji_name(id, user, &req.name).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Emoji updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn delete_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.delete_emoji(id, &user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Emoji deleted".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn get_my_request(
jar: CookieJar,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.get_user_emojis(user.id).await {
Ok(d) => Json(ApiReturn {
ok: true,
message: d.len().to_string(),
payload: Some(d),
}),
Err(e) => Json(e.into()),
}
}