2025-05-10 21:58:02 -04:00
|
|
|
use std::fs::exists;
|
|
|
|
use pathbufd::PathBufD;
|
|
|
|
use crate::{
|
|
|
|
get_user_from_token,
|
2025-05-19 19:31:12 -04:00
|
|
|
image::{save_webp_buffer, Image},
|
2025-05-10 21:58:02 -04:00
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
2025-05-10 21:58:02 -04:00
|
|
|
|
|
|
|
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)
|
2025-05-11 15:20:15 -04:00
|
|
|
pub const MAXIMUM_FILE_SIZE: usize = 262144;
|
2025-05-10 21:58:02 -04:00
|
|
|
|
|
|
|
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
|
2025-05-11 15:20:15 -04:00
|
|
|
if img.0.len() > MAXIMUM_FILE_SIZE {
|
2025-05-10 21:58:02 -04:00
|
|
|
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(_) => {
|
2025-05-10 22:11:12 -04:00
|
|
|
if is_animated {
|
|
|
|
if let Err(e) = upload.write(&data.0, &img.0) {
|
|
|
|
return Json(Error::MiscError(e.to_string()).into());
|
|
|
|
}
|
|
|
|
} else {
|
2025-05-19 19:31:12 -04:00
|
|
|
if let Err(e) =
|
|
|
|
save_webp_buffer(&upload.path(&data.0).to_string(), img.0.to_vec(), None)
|
|
|
|
{
|
2025-05-10 22:11:12 -04:00
|
|
|
return Json(Error::MiscError(e.to_string()).into());
|
|
|
|
}
|
2025-05-10 21:58:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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()),
|
|
|
|
};
|
|
|
|
|
2025-05-16 16:09:21 -04:00
|
|
|
match data.update_emoji_name(id, &user, &req.name).await {
|
2025-05-10 21:58:02 -04:00
|
|
|
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()),
|
|
|
|
}
|
|
|
|
}
|